Skip to main content
Version: Next

Tags

A tag is one named value the gateway keeps current: a reading from a device, a number someone typed, a result computed from other tags, or a figure about the gateway itself. This page explains what a tag is made of and how its value comes to be, for anyone who configures tags or reads them through the API or the SDK.

Identity and paths​

Every tag has two identifiers:

FieldWhat it is
idA stable public id (a UUID). It never changes, even when the tag is renamed or moved. Other records point at the tag by this id.
pathThe tag's place in the tree, dot-separated: Line1.Motor01.Speed. The last segment is the name; everything before it is the parentPath.

Folders are tags too, with kind folder. A folder has no value; it only groups what sits under it. Tags can also sit under other tags, not only under folders: the tree in the dashboard shows a tag's children under it, which is how a status word and the bit tags that read it stay together.

Inside the gateway each tag also has a numeric id for the hot path. Those are never recycled. You see only the public id and the path.

Reserved names​

A name that starts with @ is reserved for the gateway. Two roots exist:

  • @types holds the tag types (UDTs). Types and their folders live under it; see Tag types. The folder @types.built-in is written by Scadex itself and cannot be changed.
  • @gateway holds the gateway's own diagnostics and the system tags described under System tags and gateway diagnostics.

You cannot create a tag or folder whose name starts with @.

Tag kinds​

The kind field says where a tag's value comes from. The runtime treats each kind differently; the fields that matter for one kind are grouped under a sub-object of the same name in the API (memory, derived, expression, binding).

KindValue comes fromWritable
memoryWhatever was last written to it, starting from memoryStartValueYes
driver kinds (opcua, mqtt, external)A driver module, through the tag's bindingIf the driver allows it
derivedAnother tag: its whole value, one property of it, or one bitPassed through to the source, with limits
expressionA JavaScript expression over other tagsNo
systemThe gateway itself (@gateway.performance.*)No
folderNothing; a grouping nodeNo
udtAn instance of a tag type (UDT); its members are ordinary tags under itPer member
udt-typeA tag type definition under @typesNo

Memory tags​

A memory tag holds a value the gateway stores. It starts at memoryStartValue and keeps the last value written to it in memoryValue. Setting memoryValue to null through the API resets the tag to its start value.

A memory tag has a scope:

ScopeWhere the value livesWho sees it
gateway (default)The event backbone, with memoryValue kept for reset and rebuildEveryone
userA per-user row (TagMemoryUserValue)That user's own /sync connections
sessionThe /sync connection itselfNobody else; gone when the connection closes

User- and session-scoped values are private. They never enter the shared event timeline, so they have no replay and no history, and scaling does not apply to them. Their only shared event is the start value, which is also what derived and expression tags read. A session-scoped tag can only be written over /sync.

Driver-bound tags​

A tag that a driver module supplies is owned through a neutral tuple, the binding:

FieldMeaning
moduleIdThe module that supplies the value, such as io.scadex.driver.opcua
instanceIdThe connection inside that module (a server, a broker). May be null
configurationA JSON document the driver understands: a node id, a topic and payload format, an address

Core stores the configuration as is and never interprets it. It does not know what an OPC UA node id or an MQTT topic is; only the driver does. This is why Core has no driver-specific tables or controllers, and why a third-party driver gets the same tag workflow as the built-in ones. When you create a tag through the API, the fields are producerModuleId, producerInstanceId and binding.

The kinds opcua and mqtt are the two built-in drivers' tags; the API presents their binding fields under opcua (serverId, nodeId) and mqtt (mqttServerId, topic, payloadFormat) for convenience. The kind external is any other driver's tag, with only the generic binding.

When the module that owns a tag is stopped, or removed, the tag stays. It is marked Bad with the detail "The module that supplies this value was stopped." and keeps its last value until the module runs again. Tags are never deleted by a module's lifecycle. See Architecture for the module boundary and the modules section for lifecycle.

Derived tags​

A derived tag follows a source tag named by sourceTag, which is an absolute path or a relative one (./Sibling, ../Other; a single . is the tag this one sits inside). It carries the source's quality and timestamp. Three forms exist:

  • The whole value. No property and no sourceBit. A write to the derived tag goes through to the source.
  • One property of a structured value. property names a field of an object or an element of an array, such as motor.speed or rows[0][2]. A write is refused: the tag reads a part, and the gateway cannot rebuild the whole.
  • One bit of a word. sourceBit is a bit number from 0 (the lowest) to 63, and the tag reads true while that bit is 1. The source has to hold a whole number; a signed word keeps its bit pattern and an unsigned one reads up to bit 63. A write sets or clears only that bit: the gateway reads the word, changes the bit and writes the whole word back. Two writes to different bits close together are applied one after the other and both stay. Anything the source changes between the read and the write is lost, as always when a word is shared this way. Setting sourceBit to -1 through the API stops following a bit.

A derived tag whose source has no whole-number value cannot read a bit, and says so in qualityDetail.

Expression tags​

An expression tag is a JavaScript function body, so it needs return. It reads other tags with tag(path) and tags[path]. Only literal paths in tag('...') count as dependencies; a computed path reads as undefined. The result takes the worst quality of its inputs. A script error, a result that is not a finite number, or a dependency cycle gives Bad quality with the message in qualityDetail, and the tag keeps its last value.

Expressions run in a sandbox in separate worker processes, with limits of 50,000 statements, 16 MiB and 250 ms of CPU. Expression tags covers the language and the editor; the scripting section covers the sandbox.

System tags and gateway diagnostics​

Two kinds of @gateway tags exist, and they behave differently.

@gateway.performance.memory, managedHeap, cpu, threads and busyPoolThreads are real tags of kind system. Core creates them at start and publishes a value every ten seconds through the tag runtime, so they have history like any tag, can carry alarms, and can be trended on a screen. Their units are their own (MB, %). Nobody can edit, move, delete or write them.

The other @gateway tags live only in memory, are read-only, and have no history: @gateway.version, @gateway.uptime (unit s), @gateway.module.count, and for each module @gateway.module.<id>.id, .name, .version, .state and .error, with dots in the module id replaced by _. Names are lowercase.

@gateway.time is the gateway's clock as an ISO-8601 string in the gateway's zone, to the whole second, and @gateway.tz is that zone. The clock is not stored: a replay produces it from its own position. The dashboard's clock is this tag.

Values, quality and timestamps​

Each value a tag carries has three parts:

PartMeaning
valueThe current value. Numbers, booleans, strings, byte arrays and JSON structures (objects, arrays) are all possible.
qualityGood, Uncertain or Bad, with an optional qualityDetail saying why
timestampWhen the source produced the value, in Unix milliseconds on /sync and in the REST API

Data types​

The runtime's value kinds are Null, Boolean, Int64, UInt64, Double, String, Bytes and Json. A structured value (an object a controller sends over OPC UA, a JSON payload from MQTT) is one value of kind Json; a derived tag with a property reads a field out of it, and a tag type can name such a member as the one that holds the whole structure (see Tag types). The dataType field on a tag is a number the driver reports from the source, shown in the Data type column; it is kept as metadata.

Quality​

QualityMeaning
GoodThe value can be trusted
UncertainThe value is there but something is wrong with how it was produced; alarms can go active on it but not clear
BadThe value cannot be trusted. The last value is kept so you can see what it was

What makes a tag Bad or Uncertain:

  • The module that supplies it was stopped or removed (Bad, "The module that supplies this value was stopped.").
  • Its expression threw, returned something that is not a finite number, or depends on itself (Bad, with the script error in qualityDetail).
  • Its scaling configuration is wrong, such as equal raw limits (the source value passes through Uncertain, "Scaling is not applied: ...").
  • A bit tag whose source is not a whole number (no bits to read).
  • The driver itself reports a bad or uncertain status from the device.

Alarms treat quality as described in Alarms: an Uncertain value can raise an alarm but not clear it, and a Bad value changes nothing and marks outstanding alarms stale.

Scaling and deadband​

Scaling turns the source's raw value into an engineering value. It runs inside the tag runtime before the value gets its sequence number, so the event timeline, the current state, /sync, replay and history all hold the engineering value. The raw value travels with it as rawValue.

The settings are in the tag's numeric object:

FieldMeaning
scaleModeoff, linear, squareRoot, exponentialFilter, bitInversion or map
rawLow, rawHighThe raw range for linear and squareRoot
engLowLim, engHighLimThe engineering range (default 0 to 100)
engLimitModenoClamp (default) or clamp: whether the result is held inside the engineering range
scaleFactorThe factor for exponentialFilter
bitCountThe word width for bitInversion
scaleMapFor map: a JSON array of { raw, scaled } pairs
deadbandModeoff, absolute or percent
deadbandThe deadband size

A deadband is checked after scaling. A value that moves less than the deadband from the last one produces no event at all. It never suppresses a quality change or a write. In percent mode the percentage is of the last value.

Writes are inverse-scaled before they reach the source. A write is refused rather than altered when the value is outside a clamped range, when the limits are equal, or when a value map has no single raw value for it. An exponential filter has no inverse, so a write passes through as written.

Bad scaling configuration passes the source's value through as Uncertain and refuses writes. Filter state and the deadband's last value are in memory, so both restart with the gateway.

Units, format and description​

FieldWhereMeaning
engUnitnumericThe engineering unit, shown beside the value
formatStringnumericHow the value is formatted for display, such as 0 for a whole number
tooltipmetaA short description, shown in the Description column
documentationmetaLonger notes
enabledtagWhether the runtime reads and publishes the tag

History settings​

A tag also carries a history row (enabled, storageProvider, sampleMode, minTimeBetweenSample, maxTimeBetweenSample, deadband style and mode). These settings are stored but nothing acts on them yet: the historian records every accepted value of every tag, and history reads come from the historian or the event backbone, not from this table. See History and replay.

Enabling and disabling​

A disabled tag (enabled: false) is not read and publishes nothing; the dashboard shows it as Disabled. Disabling a tag clears anything outstanding on its alarms, with the reason recorded in alarm history. A tag type member has an enabled default that an instance can override.

Writes​

Every change to a tag's value goes through one path in the runtime, whether it comes from a screen, the REST API, a script or a module. Clients never talk to a driver directly.

  • A write needs the operator role or higher. On /sync it is tag.write {path, value}; over REST it is POST /api/v1/runtime/tags/{id}/write.
  • Every write is recorded against the principal who asked for it before it is attempted, with the outcome (accepted, rejected, failed or pending). A write that cannot be recorded is not attempted. The dashboard shows a tag's writes in its Audit tab, for admins. See Audit log.
  • A write to a scaled tag is inverse-scaled first, and may be refused, as described above.
  • A write to a derived tag that follows a property is refused; one that follows a bit changes only that bit.
  • Writes are refused while the connection is in a replay.
  • POST /api/v1/runtime/tags/{id}/values injects a reading as if the source had reported it. It needs the engineer role and is audited with operation publish.

A value becomes current only after the event backbone has durably accepted it, and only then do subscribers, alarms and the historian see it. Architecture follows that path.

The tag browser​

The dashboard's Tags page shows every tag in one tree with three groups: the gateway's own tags, the types under @types, and the project's tags. Selecting a row opens it in a side panel beside the tree; there are no modal editors.

Columns. Tag, Description, Quality, Current value, Data type and Timestamp. Value, quality and timestamp are live, over /sync. Quality shows as Good, Disabled, No value, or the detail of an Uncertain or Bad value.

Tree. Folders open and close. An instance of a tag type shows its members under it, and tags that were added to an instance are marked as added. A tag sits under the tag it belongs to, not only under folders: the bit tags of a status word are listed under the word, in bit order, lowest first, and the word shows a bit map of which bits are read.

Search and filters. Search tags finds a tag by name or path. Filters narrow the tree by kind: OPC UA, MQTT, Memory, Derived, Expression, UDT instance, Driver.

Menu actions on a row or a folder: Add tag, New folder, New type, Duplicate, Copy, Enable, Disable, Delete. Rows can be dragged to move them; the API behind that is POST /api/v1/tags/{id}/move with the new parent path, and a copy lands through POST /api/v1/tags/{id}/copy with a new name and target parent. Deleting a folder or an instance deletes everything under it.

Side panel. The editor has sections for general settings, the binding and scaling, and tabs for the type's parameters and instances where they apply, the tag's history, its write audit (admins), its metadata and its raw JSON. Changing a field saves it to the tag; a member of a type instance shows a dot beside each field that the instance overrides, so you can see what differs from the type.

Restart. Restart on a tag, a folder, an instance or a type makes the source send its current value again: drivers re-create their subscriptions and computed tags are rebuilt (POST /api/v1/tags/{id}/restart, operator).

Import and export​

A subtree can be exported as JSON (GET /api/v1/tags/export?path=) and imported under another path (POST /api/v1/tags/import with targetPath and the exported data). The file carries each tag's path relative to the exported root, its kind, binding, scaling and metadata (version: 1, type: "tag-export"). Types have their own export, described in Tag types. The REST details are in Tags API.