Skip to main content

Transform Adapter Scripts


This guide explains how to use Transform Adapter Scripts in MachineMetrics to manipulate, combine, and derive new data from machine signals.


1. What Are Adapter Scripts?

Adapter scripts are configuration and logic documents written in YAML that control how MachineMetrics reads and processes machine data. They serve two primary purposes:

  1. Configure Connectivity — Specify which tags, registers, or data points to read from a machine
  2. Transform Data — Manipulate, combine, or synthesize data into new signals

Script Structure: Input → Transformation → Output

Every adapter script is organized into three conceptual sections that flow in sequence:

SectionPurposeKey Blocks
InputConfigure connectivity; declare which data to readtags, registers, coils, pins (direct) or declare-keys (transform)
TransformationDefine variables that process and combine datavariables, generators
OutputDeclare what gets sent to MachineMetricsdata-items, conditions

The order of top-level blocks in the script does not matter, but following this Input → Transformation → Output order by convention makes scripts easier to read and understand.

Input is everything related to connectivity. Depending on the adapter type, this means specifying which registers to read on a Modbus connection, which tags to read on an OPC-UA connection, or which data items to pull from a parent adapter. Each piece of data in this section is assigned an identifier that can be referenced in the transformation or output sections.

Transformation is where new variables are defined to transform data. Variables can read the values of other variables or input identifiers, and chain operations together (threshold → rising-edge → count, for example).

Output defines what data is sent to MachineMetrics. Input data and variables do not automatically get exported — they must be explicitly listed in data-items or conditions.

version: 2

# Input — declare what to read
tags:
spindle-on: Local:System.SpindleRunning
fault-ind: Local:System.FaultIndicator

# Transformation — derive new values
variables:
execution:
- state:
- ACTIVE: spindle-on and fault-ind == 0
- INTERRUPTED: fault-ind > 0
- READY: true

# Output — declare what to send to MachineMetrics
data-items:
- execution
- spindle-on

When to Use Adapter Scripts

Use CaseExample
Combine multiple signals into one stateSpindle + coolant + door → execution state
Count parts from a pulsing signalRising edge detection on analog input
Convert sensor valuesTemperature C → F, voltage → pressure
Create custom alarms/conditionsLow coolant warning from analog level
Filter or clean dataIgnore warmup programs, remove noise
Derive calculated metricsEfficiency from multiple inputs

Use AI to Generate Scripts

Max — MachineMetrics' AI assistant — can write a complete adapter script from a plain-language description of what you need. The more precisely you describe your setup, the more accurate the script will be on the first try.

Access Max using the Ask Max button in the top navigation bar, or by clicking the chat icon in the bottom-right corner of this page.

There are two common situations where you would ask Max to generate a script:

ScenarioWhat it doesExamples
Scenario 1: Hardware AdapterReads physical signals or registers and maps them to MachineMetrics dataSealevel I/O, WISE, Modbus PLC, OPC-UA
Scenario 2: Transform AdapterTakes an existing data item and extracts or reshapes its valueParse operation number from a FANUC header, extract job ID from a program name

Scenario 1: Hardware Adapter Scripts

Use this scenario when you are reading signals from hardware — a Sealevel or WISE I/O module, a Modbus PLC, an OPC-UA server, or similar — and need a script that maps those signals to execution state, part count, and other MachineMetrics data items.

What to Tell the AI

Work through these four areas before starting your chat.

1. Hardware and Protocol

Tell Max what device you are reading from:

  • Sealevel eI/O-170E / eI/O-170PoE — analog and digital I/O module
  • Advantech WISE-4050 or WISE-4050/LAN — wireless or wired I/O module
  • Modbus TCP device — PLC, gateway, or controller (include make/model if known)
  • OPC-UA server — include namespace info if you have it
  • FANUC FOCAS, EtherNet/IP, or other CNC protocol — mention the controller model

2. What Is Wired Where

For each signal you are using, tell Max:

  • Which terminal or channel — e.g., Opto 1, Opto 2, AN1, DI0, register 40005
  • What is physically connected — e.g., spindle contactor relay, part ejector relay, green stack light, encoder, current transducer
  • Signal type — analog voltage (0–10V CT), digital wet contact (machine supplies 24VDC), digital dry contact (relay closure, no voltage on wire), or Modbus register/coil

3. What Each Signal Means — the Most Important Part

Describe in plain English what should happen in MachineMetrics when each signal activates:

"When Opto 1 is ON, the machine is cutting — mark it as ACTIVE. When Opto 1 goes OFF, wait 10 seconds before switching to READY so brief tool changes don't drop the state."

"Opto 2 pulses briefly (less than 1 second) each time a part is ejected. Each pulse should count as one finished part."

"AN1 is a 0–10V current transducer on the spindle motor cable. When the voltage rises above 1.5V the machine is running and should be ACTIVE."

"Register at address 0, function code 3 on the WISE is the DI0 hardware counter. When it increases, parts are being made — derive execution state from counter activity with a 10-second hold."

4. Any Special Logic

  • Should parts only count when the machine is also in auto mode?
  • Do you need to ignore signals during setup programs or warmup?
  • Are there multiple signals that together determine state — e.g., green light AND not in alarm?
  • Is there a timing requirement like an off-delay to absorb tool changes?

Example: A Good Prompt

"I'm using a Sealevel eI/O-170E. Opto 1 is wired to the normally-open contact of a relay that closes when the spindle contactor energizes — so when Opto 1 is ON the machine is running and should be ACTIVE, when OFF it should be READY. Add a 10-second off-delay so brief tool changes don't drop to READY. Opto 2 is wired to a part ejector relay that pulses for about half a second each time a part comes off — count each ON pulse as one completed part. AN1 is a 0–10V current transducer on the spindle motor cable; output the voltage as a data item for trending."

Max will respond with a complete, commented adapter script covering all three signals.

Example: A Vague Prompt (Avoid This)

"I have a Sealevel and need execution and part count."

This tells Max almost nothing about wiring, signal types, thresholds, or logic. The result will be a generic template that still needs significant manual editing. Two extra minutes of description saves far more time than editing a vague script.

Quick-Reference Checklist

Fill this in for each signal before opening a chat with Max:

QuestionExample Answer
Hardware / protocol?Sealevel eI/O-170E
Terminal or channel?Opto 1
What is connected to it?Spindle contactor relay, normally-open contact
Signal type?Dry contact
What does the signal being ON mean?Machine spindle is running
MachineMetrics state or action?ACTIVE execution state
Any timing?10-second off-delay
Conditions or filtering?None for this signal

Repeat for every input: Opto 2, AN1, DI0, a register address, and so on.


Scenario 2: Transform Adapter Scripts

Use this scenario when you already have a working machine connection (FANUC FOCAS, OPC-UA, Modbus, etc.) and want to take one of its existing data items and extract, reformat, or derive something new from it — without touching the original adapter.

A transform adapter sits on top of the parent adapter and only sees the data items you declare. No hardware addresses or registers are involved.

Common examples:

  • A FANUC machine sends a header field like CUSTOMER/JOB-5678/OP30 — you want to extract just OP30 as a data item called operation
  • A program name follows a pattern like P12345-REV-A — you want just the part number 12345
  • An alarm code arrives as a raw number — you want a human-readable label based on ranges
  • You want to combine spindle speed and feed rate into a single derived efficiency metric

What to Tell the AI

1. The Parent Adapter Type

Tell Max which adapter is already collecting data on this machine:

  • FANUC FOCAS, Mitsubishi, Mazak, Okuma, DMG MORI, etc.
  • OPC-UA, Modbus, EtherNet/IP
  • This tells Max to generate a declare-keys style script, not a hardware adapter

2. The Source Data Item Name

Tell Max the exact name of the data item that contains the value you want to work with. This is the name as it appears in MachineMetrics — you can find it in the machine's Diagnostics tab.

"The data item is called program_comment."

"The data item is called header."

3. A Real Example of What It Contains

Paste an actual sample of the value — this is more useful than describing it abstractly. Max can infer the pattern from a real example.

"Here is an example of what the header contains: CUSTOMER-ABC/JOB-5678/OP30"

"The program comment looks like this: (PART:A5678 OP:10 REV:B)"

4. What You Want Extracted — Give a Rule, Not Just an Example

Describe the extraction rule in plain English rather than just naming the value you want. A rule generalizes to every future value the field might contain.

Instead of this...Say this...
"Extract OP30""Extract everything after the last /"
"Get A5678""Extract the value after PART:"
"Pull out 10""Extract the digits after OP:"
"Get the part number""Extract the first sequence of digits in the string"

5. What to Call the Output Data Item

Tell Max the name you want for the new data item in MachineMetrics:

"Call the output operation."

"Output it as part_number."

6. Any Additional Handling

  • Should the extraction fail gracefully if the pattern is not present?
  • Do you want to pass any other data items from the parent adapter through unchanged?
  • Should any data items from the parent be blocked from MachineMetrics?

Example: A Good Prompt

"I have a FANUC FOCAS machine. There is a data item called program_comment that contains a header string. Here is a real example of what it looks like: CUSTOMER-ABC/JOB-5678/OP30. I want to extract the operation number — which is always the segment after the last / — and output it as a new data item called operation. Use a rule-based extraction, not hardcoded text, so it works for any header that follows this format."

From this, Max will generate a transform adapter using declare-keys and a pattern-match regex that extracts everything after the final /.

Example: A Vague Prompt (Avoid This)

"I need to get the operation from the program comment."

Max does not know what the data item is named, what the comment actually looks like, where in the string the operation lives, or what rule to apply. Always provide the data item name, a real sample value, and a plain-English extraction rule.

Quick-Reference Checklist

QuestionExample Answer
Parent adapter type?FANUC FOCAS
Source data item name?program_comment
Sample of the actual value?CUSTOMER-ABC/JOB-5678/OP30
Extraction rule (not just the target value)?Everything after the last /
Output data item name?operation
Pass other data items through?Yes, pass execution and part_count through unchanged
Block any items?No

Ready to generate your script?

Click the Ask Max button in the navigation bar. Tell Max which scenario applies, then paste in your answers to the relevant checklist. The more concrete your description — especially a real sample value and a rule rather than just an example — the better the script will be on the first attempt.


2. Adapter Script Structure

Two Types of Adapter Scripts

MachineMetrics uses two types of adapter scripts:

  1. Direct Connectivity Adapters — Connect directly to machine protocols (FANUC FOCAS, Modbus, OPC-UA, EtherNet/IP)
  2. Transform Adapters — Pull data items from a parent adapter and transform them

Direct Connectivity Adapter Structure

Direct connectivity adapters read data directly from machines:

# ===== INPUT SECTION =====
# Configure connectivity and declare data sources
tags:
recipe_loaded: RecipeLoaded
mode_auto: Module10.ModeAuto
fault_ind: Module10.FaultInd

# ===== TRANSFORMATION SECTION =====
# Define variables that process and combine data
variables:
execution:
- state:
- ACTIVE: mode_auto and fault_ind == 0 and recipe_loaded
- READY: true

# ===== OUTPUT SECTION =====
# Declare what gets sent to MachineMetrics
data-items:
- recipe_loaded
- execution

Transform Adapter Structure

Transform adapters use declare-keys to pull data items from a parent adapter (such as a FANUC FOCAS adapter), then transform them:

version: 2

# ===== INPUT SECTION =====
# Pull data items from parent adapter (e.g., FOCAS)
declare-keys:
- program_comment

# ===== TRANSFORMATION SECTION =====
# Extract operation from program comment using regex
variables:
operation:
- source: program_comment
- pattern-match:
pattern: /\(([^)]+)\)/
group: 1

# ===== OUTPUT SECTION =====
# Output the transformed data item
data-items:
- operation

Key Difference: Transform adapters do not use tags, registers, coils, or pins. Instead, they use declare-keys to reference data items that already exist in the parent adapter.

Section Breakdown

Adapter TypeInput SectionPurpose
Direct Connectivitytags, registers, coils, pinsRead data directly from machine protocols
Transformdeclare-keysPull existing data items from parent adapter
BothvariablesProcess and combine data
Bothdata-items, conditionsSend data to MachineMetrics

3. Data Types

Understanding data types is essential for writing effective adapter scripts.

Core Data Types

TypeDescriptionExample
BooleanTrue/false valuestrue, false, digital I/O signals
NumberIntegers or decimals42, 3.14, analog readings
StringText values"ACTIVE", "Program123"
ArrayList of values[1, 2, 3], register arrays
ObjectNamed properties{temperature: 72, pressure: 30}

Special Type: Unavailable

Any data can enter an unavailable state when:

  • The data source is disconnected
  • An expression error occurs
  • The literal string UNAVAILABLE is encountered

Important: Unavailable is "infectious" — if any identifier in an expression becomes unavailable, the entire result becomes unavailable and cascades through the script.

Additional Types Used in Parameters

Some operation parameters accept types that are not general data types — data itself will never have these types, but they appear frequently in script syntax:

Pattern

A pattern is a regular expression (regex) used for matching or replacement. When a pattern is required, it must be a valid ECMA (JavaScript) regular expression surrounded by forward slashes:

/\d+/

Regex flags i, m, u, and s can be added after the closing slash (e.g., /pattern/i). The g flag is controlled automatically by the operation.

Interval

An interval is a length of time expressed as a number and a unit:

UnitMeaning
msmilliseconds
sseconds
mminutes
hhours
ddays
wweeks

Example: 10s, 5m, 8h. If no unit is given, seconds is assumed.

warning

Interval syntax is not accepted everywhere. In places where it is not supported, time intervals must be provided as a plain number representing seconds.

String Expression

A string expression is a string that contains one or more embedded expressions using ${} syntax. Each expression is evaluated and its result replaces the placeholder in the string:

The temperature is ${temperature} and the machine is ${execution}

More complex expressions work too:

${partcount1 + partcount2} parts completed

String expressions are used in condition code and message fields, among other places.

Type Conversions

  • Data exported as data-items is converted to strings for MTConnect compatibility
  • Operations automatically convert between compatible types (e.g., string "42" → number 42)
  • String comparisons are case-sensitive

4. Declaring Input Keys

The input section varies based on whether you're creating a direct connectivity adapter or a transform adapter.

Transform Adapters: Using declare-keys

Transform adapters pull existing data items from a parent adapter (such as FANUC FOCAS, Modbus, or OPC-UA) and transform them.

Important: Transform adapters use declare-keys, not tags, registers, coils, or pins.

version: 2

# Pull data items from parent adapter
declare-keys:
- program_comment
- tool_number
- spindle_override

# Transform the data
variables:
operation:
- source: program_comment
- pattern-match:
pattern: /\(([^)]+)\)/
group: 1

tool_status:
- source: tool_number
- expression: tool_number > 0

# Output transformed data
data-items:
- operation
- tool_status

How declare-keys Works:

  1. Parent adapter (e.g., FANUC FOCAS) collects data items like program_comment, tool_number, spindle_override
  2. Transform adapter declares which data items it needs using declare-keys
  3. Transform adapter processes those data items through variables
  4. Transform adapter outputs new or modified data items

Use Cases for Transform Adapters:

  • Extract operation number from program comment using regex
  • Parse part number from program name
  • Convert units or formats
  • Combine multiple data items into derived metrics
  • Clean or filter data before sending to MachineMetrics

deny-keys: Excluding Data Items

Use deny-keys to explicitly exclude data items from being passed through from the parent adapter.

version: 2

# Pull specific data items
declare-keys:
- program_comment
- execution
- part_count

# Exclude specific data items from parent adapter
deny-keys:
- raw_alarm_code # Don't pass through raw codes
- debug_flag # Internal use only

variables:
operation:
- source: program_comment
- pattern-match:
pattern: /\(([^)]+)\)/
group: 1

data-items:
- operation
- execution
- part_count

When to Use deny-keys:

  • Block internal/debug data items from reaching MachineMetrics
  • Prevent duplicate or conflicting data items
  • Filter out raw data that will be replaced by transformed versions
  • Control exactly which data items are exposed

mtconnect-passthrough: Passing Parent Data Through

Use mtconnect-passthrough: true to automatically pass all data items from the parent adapter through the transform adapter, in addition to any transformed data items.

version: 2

# Pass all parent adapter data items through
mtconnect-passthrough: true

# Only declare keys you need to transform
declare-keys:
- program_comment

# Transform specific data items
variables:
operation:
- source: program_comment
- pattern-match:
pattern: /\(([^)]+)\)/
group: 1

# Output transformed data (parent data also passed through)
data-items:
- operation

How mtconnect-passthrough Works:

  1. Parent adapter collects many data items (e.g., 50+ from FANUC FOCAS)
  2. Transform adapter sets mtconnect-passthrough: true
  3. All parent data items are automatically passed to MachineMetrics
  4. Transform adapter only needs to declare-keys for items it wants to transform
  5. Both original parent data AND transformed data are available in MachineMetrics

Benefits:

  • Don't need to manually list every data item to pass through
  • Parent adapter data flows through unchanged
  • Only declare and transform the specific items you need to modify
  • Simplifies transform adapter scripts

Example Use Case:

# Parent FOCAS adapter provides 50+ data items
# We only want to transform program_comment → operation
# But we want all other FOCAS data items (spindle_speed, tool_number, etc.)

version: 2
mtconnect-passthrough: true # Pass everything through

declare-keys:
- program_comment # Only declare what we're transforming

variables:
operation:
- source: program_comment
- pattern-match:
pattern: /\(([^)]+)\)/
group: 1

data-items:
- operation # New data item added to the parent's 50+ items

Result: All parent FOCAS data items PLUS the new operation data item are sent to MachineMetrics.


Direct Connectivity Adapters

Note: The following input types (tags, registers, coils, pins) are ONLY used in direct connectivity adapters that connect to machine protocols (EtherNet/IP, Modbus, OPC-UA, FANUC FOCAS).

Transform adapters do NOT use these. Transform adapters use declare-keys, deny-keys, and mtconnect-passthrough instead.

Direct connectivity adapters read data directly from machine protocols. Here are common input patterns:

EtherNet/IP Tags

version: 2
slot: 1
scan-interval: 0.25
tags:
recipe_loaded: RecipeLoaded
mode_auto: Module10.ModeAuto
peak_force: Program:Station1.Values.Reals[0]

Modbus Registers and Coils

version: 2
unit-id: 1
byte-order: big
word-order: big
coils:
exec-bit:
address: 10001
registers:
counter:
address: 40005
type: uint32
program-name:
address: 40101
type: string
size: 80

OPC-UA Tags

version: 2
tags:
spindle-speed: ns=2;s=Spindle.Speed
coolant-level: ns=2;s=Coolant.Level
program: ns=2;s=Controller.Program

Key Naming Rules

  • Use lowercase with hyphens: spindle-speed, part-count
  • Avoid spaces and special characters
  • Names become identifiers used throughout the script

5. Variables

Variables transform data through a chain of operations. Each operation takes input from the previous step and passes output to the next.

Basic Variable Structure

variables:
part-counter:
- source: AIN0 # Step 1: Read analog input
- threshold: 2.5 # Step 2: Convert to boolean (true if > 2.5)
- rising-edge # Step 3: Detect low→high transitions
- count # Step 4: Increment counter on each edge

Reading order: Top to bottom. Each operation takes the output of the previous step as its input. The last operation's result becomes the value of the variable.

Starter Operations

The first operation in a variable must be one of these "starter" operations that watch for data changes:

OperationPurposeExample
sourceReference another identifier or expressionsource: spindle-speed
expressionCalculate from multiple sourcesexpression: (temp * 9/5) + 32
stateMap conditions to state valuesSee state machine example below

State Machine Example

Create execution states from multiple signals:

variables:
execution:
- state:
- ACTIVE: mode_auto and fault_ind == 0 and recipe_loaded
- INTERRUPTED: fault_ind > 0
- READY: true # Default fallback

States are evaluated top-to-bottom; first true condition wins.

Shorthand Syntax

Simple variables can use shorthand:

# Full syntax
variables:
temp-f:
- source: (temp-c * 9 / 5) + 32

# Shorthand (equivalent)
variables:
temp-f: (temp-c * 9 / 5) + 32

6. Expressions and Operators

Expressions calculate values using operators, functions, and identifiers.

Supported Operators

CategoryOperatorsExample
Arithmetic+, -, *, /, % (modulus), ^ (power)(temp * 9/5) + 32
Booleanand, or, notspindle-on and coolant-on
Bitwise&, |, ~, ^|, <<, >>, >>>flags & 0x0F
Relational>, <, >=, <=, ==, !=temp > 100
Conditional? : (ternary)mode ? "AUTO" : "MANUAL"

Expression Examples

# Temperature conversion
(temp-in * 9 / 5) + 32

# Check if either spindle is running
spindle-1 or spindle-2

# Range check
AIN0 > 1.2 and AIN0 < 3.4

# String comparison
exec == 'STOPPED'

# Conditional/ternary
hv-relay-on ? hv-analog : (lv-analog * 10)

The this Keyword

Within variable operation chains, this refers to the result of the previous step. It is not available in the first operation (usually source):

variables:
temp-f:
- source: temp-c
- expression: (this * 9 / 5) + 32 # 'this' = temp-c value

Special Identifiers

The engine provides two special identifiers available anywhere expressions are used:

  • device-connected — A boolean that is true when the device is fully connected. Matches the avail value exported by MTConnect adapters.
  • this — The result of the previous operation step in a variable chain (see above).

Property Access

Access object properties with dot notation or brackets:

# Dot notation
this.temperature

# Bracket notation (for names with special characters or dots)
system['X.01'].message

# Chained nested access
oil.system.temperature

For condition identifiers coming from MTConnect or array sources, you can index by alarm code:

condName.X01.level        # dot notation
condName['X02'].message # bracket notation for codes needing quoting

Array Indexing

When indexing arrays within expressions in the adapter script engine, indexes are 1-based — the first element is at index 1, not 0:

alarm-codes[1]   # first element
items[2] # second element
Protocol tag paths use their own indexing rules

This 1-based rule applies only to arrays produced or handled by the adapter script expression engine. Protocol-specific tag or address syntax — such as EtherNet/IP tag paths like Program:Station1.Values.Reals[0] — follows the indexing convention of that protocol (typically 0-based). These are written in the tags input section and are not evaluated by the expression engine.

Common Functions

FunctionPurposeExample
abs(x)Absolute valueabs(spindle-speed)
round(x, n)Round to n decimalsround(temp, 2)
floor(x)Round downfloor(count)
ceil(x)Round upceil(count)
min(a, b)Smaller valuemin(speed1, speed2)
max(a, b)Larger valuemax(speed1, speed2)

7. Transform Operations Reference

This comprehensive section documents all available transform operations with examples.

Overview of Operations

Operations transform data through sequential processing. Each operation takes input from the previous step and outputs to the next. Operations are grouped by category below.


7.1 Source Operations (Starters)

These operations must be first in a variable's operation chain. They watch for data changes and trigger subsequent operations.

source

References another identifier or expression. Most common starter operation.

variables:
temp-f:
- source: temp-c
- expression: (this * 9 / 5) + 32

expression

Calculates a value from multiple sources. Alternative to source when you need complex calculations.

variables:
total-speed:
- expression: spindle-1-speed + spindle-2-speed

state

Maps conditions to state values. Conditions are evaluated top-to-bottom; first true wins.

Basic Execution State:

variables:
execution:
- state:
- ACTIVE: spindle-on and door-closed and not alarm-active
- INTERRUPTED: alarm-active
- READY: true # Default fallback

Execution with Manual/Auto Modes:

variables:
execution:
- state:
- JOG: mode-switch == 1
- AUTO: mode-switch == 2
- MANUAL: mode-switch == 0
- READY: true

Using Modbus Register for Execution:

# Modbus register values: 0=Manual, 1=Jog, 2=Auto
registers:
mode_register:
address: 40001
type: uint16

variables:
execution:
- state:
- JOG: mode_register == 1
- AUTO: mode_register == 2
- MANUAL: mode_register == 0
- READY: true

7.2 Boolean Logic Operations

and

Logical AND - true if all conditions are true.

variables:
system-ready:
- source: power-on and coolant-on and door-closed

or

Logical OR - true if any condition is true.

variables:
any-spindle-on:
- source: spindle-1 or spindle-2 or spindle-3

not / invert

Inverts boolean value.

variables:
door-open:
- source: door-closed
- invert # Flip the signal

toggle

Flips between true/false on each true input.

variables:
alternate-output:
- source: trigger-signal
- toggle # Alternates between true/false each time triggered

7.3 Edge Detection Operations

rising-edge

Produces true pulse when input goes from false→true.

variables:
part-complete:
- source: cycle-sensor
- threshold: 2.5
- rising-edge # Only true at the moment of transition

falling-edge

Produces true pulse when input goes from true→false.

variables:
door-opened:
- source: door-sensor
- falling-edge # Triggers when door opens (signal goes low)

edge

Produces true pulse on ANY transition (low→high OR high→low).

variables:
any-change:
- source: sensor-input
- edge # Triggers on any state change

value-change

Triggers when value changes (not just boolean).

variables:
program-changed:
- source: program-name
- value-change # Triggers whenever program name changes

value-increase

Triggers when value increases.

variables:
counter-incremented:
- source: part-counter
- value-increase # True whenever counter goes up

value-increase-diff

Returns the amount of increase when value goes up.

variables:
parts-added:
- source: part-counter
- value-increase-diff # Returns how many parts were added

value-decrease

Triggers when value decreases.

variables:
counter-decremented:
- source: reject-counter
- value-decrease

7.4 Threshold and Comparison Operations

threshold

Converts analog to boolean using a cutoff value.

variables:
spindle-running:
- source: spindle-rpm
- threshold: 100 # true if > 100, false if ≤ 100

With hysteresis to prevent oscillation:

variables:
coolant-adequate:
- source: coolant-level
- threshold:
value: 50 # Turn on at 50
hysteresis: 10 # Turn off at 40 (50-10)

7.5 Timing Operations

debounce

Filters out changes shorter than specified duration.

variables:
stable-door:
- source: door-sensor
- debounce: 0.5 # Ignore bounces shorter than 500ms

on-delay

Delays true signal, passes false immediately.

variables:
confirmed-running:
- source: spindle-on
- on-delay: 2 # Must be on for 2 seconds before passing true

off-delay

Holds true signal after input goes false. Useful for preventing state flicker during tool changes or brief interruptions.

Prevent Execution Flicker During Tool Change:

variables:
stable-execution:
- source: spindle-running
- off-delay: 5 # Holds ACTIVE state for 5 seconds after spindle stops
# Prevents execution from flickering to READY during brief tool changes

Hold Machine Running State:

variables:
machine-active:
- source: any-axis-moving or spindle-on
- off-delay: 3 # Keeps "running" for 3 seconds after motion stops

Modbus Example - Stabilize Noisy Execution Signal:

registers:
exec_status:
address: 40010
type: uint16

variables:
execution:
- source: exec_status > 0
- off-delay: 10 # Hold execution ON for 10 seconds after signal drops
# Eliminates noise from PLC that briefly drops signal

7.6 Counting and Accumulation Operations

count

Increments counter on each true input. Resets when reset condition becomes true.

variables:
part-count:
- source: part-sensor
- rising-edge
- count

With amount and reset:

variables:
shift-parts:
- source: part-complete
- count:
amount: 1
reset: shift-start # Reset at shift start

Count by variable amount:

variables:
total-parts:
- source: batch-complete
- count:
amount: batch-size # Increment by batch-size each time
reset: new-order

accumulate

Sums values over time.

variables:
total-weight:
- source: weight-reading
- accumulate # Running total of all weight readings

With reset:

variables:
daily-production:
- source: part-weight
- accumulate:
reset: day-end # Reset at end of day

window-count

Counts events within a sliding time window.

variables:
parts-per-minute:
- source: part-complete
- window-count: 60 # Count parts in last 60 seconds

7.7 Statistical Operations

average

Calculates average of values over time.

variables:
avg-temp:
- source: temperature
- average:
samples: 10 # Average last 10 readings

min

Returns minimum value.

variables:
min-pressure:
- source: pressure
- min:
samples: 20 # Minimum of last 20 readings

max

Returns maximum value.

variables:
max-speed:
- source: spindle-speed
- max:
samples: 100 # Maximum of last 100 readings

min-delta

Only passes values if change exceeds minimum threshold. Reduces noise and data volume.

variables:
significant-temp-change:
- source: temperature
- min-delta: 5 # Only update if temp changes by 5+ degrees

rate-of-change

Calculates rate of change per second.

variables:
temp-rise-rate:
- source: temperature
- rate-of-change # Degrees per second

7.8 String and Pattern Operations

pattern-match

Extracts text using regex patterns. Essential for parsing program comments and names.

Extract Operation from Program Comment:

# Input: "O1234(OP10-ROUGH-MILL)"
# Output: "OP10-ROUGH-MILL"
variables:
operation:
- source: program_comment
- pattern-match:
pattern: /\(([^)]+)\)/
group: 1 # Capture group 1 (text inside parentheses)

Extract Part ID from Program Comment:

# Input: "PART-12345-REV-A"
# Output: "12345"
variables:
part-id:
- source: program_comment
- pattern-match:
pattern: /PART-(\d+)/
group: 1 # Capture the digits after "PART-"

Multiple Part ID Patterns:

# Handles formats: "P12345", "PART-12345", "PN:12345"
variables:
part-number:
- source: program_comment
- pattern-match:
pattern: /P(?:ART-?|N:)?(\d+)/i
group: 1

Extract Work Order from Program Name:

# Input: "WO-45678-OP20"
# Output: "45678"
variables:
work-order:
- source: program
- pattern-match:
pattern: /WO-(\d+)/
group: 1

Extract Multiple Fields:

# Input: "O1234(OP10-MILL)(PART:A5678)(REV:B)"
variables:
operation:
- source: program_comment
- pattern-match:
pattern: /\(OP(\d+)[^)]*\)/
group: 1 # Extracts "10"

part-id:
- source: program_comment
- pattern-match:
pattern: /PART:([A-Z0-9]+)/
group: 1 # Extracts "A5678"

revision:
- source: program_comment
- pattern-match:
pattern: /REV:([A-Z])/
group: 1 # Extracts "B"

Common Regex Patterns:

# Digits only: \d+
# Letters and numbers: [A-Z0-9]+
# Any characters except parentheses: [^)]+
# Optional dash or colon: [-:]?
# Case insensitive: /pattern/i

pattern-replace

Replaces text using regex patterns.

# Replace underscores with spaces
variables:
clean-name:
- source: program-name
- pattern-replace:
pattern: /_/g
replacement: " "

Remove prefix:

# Remove "PROG-" prefix from program names
variables:
short-name:
- source: program
- pattern-replace:
pattern: /^PROG-/
replacement: ""

pattern-test

Tests if pattern matches (returns boolean).

variables:
is-setup-program:
- source: program
- pattern-test:
pattern: /SETUP|WARMUP/i # Case insensitive check

pattern-escape

Escapes special regex characters in a string.

variables:
safe-pattern:
- source: user-input
- pattern-escape # Makes string safe for use in regex

trim

Removes leading and trailing whitespace.

variables:
clean-comment:
- source: program_comment
- trim

max-length

Truncates string to maximum length.

variables:
short-message:
- source: alarm-message
- max-length: 50 # Limit to 50 characters

7.9 Data Selection and Filtering Operations

ignore-value

Blocks specific values from passing through.

variables:
filtered-program:
- source: program
- ignore-value: "WARMUP.NC" # Don't output when program is WARMUP.NC

Ignore multiple values:

variables:
production-programs:
- source: program
- ignore-value: ["WARMUP.NC", "SETUP.NC", "PROBE.NC"]

reject

Blocks values when condition is true (opposite of ignore-value).

variables:
valid-readings:
- source: temperature
- reject: this < 0 or this > 500 # Reject invalid readings

when-unavailable

Provides fallback value when data becomes unavailable.

variables:
safe-temp:
- source: temperature
- when-unavailable: 0 # Use 0 if sensor disconnects

latch-value

Latches (holds) an incoming value whenever a reset expression evaluates true, including the first value seen after the expression evaluates false. The reset expression is tested on every incoming value and whenever any variables within the expression itself change.

Syntax:

- latch-value:
reset: expression
reset-value: constant value
latch-on-change: boolean
  • reset (required): Expression tested on every incoming value. Each time it evaluates true, the latch is set "open" so it will latch the next incoming value.
  • reset-value (optional): When the reset expression is true, hold this value — but still latch the next incoming value once the expression evaluates false again.
  • latch-on-change (optional, default false): When true, an open latch remains open until the incoming value is different from the value present when the reset expression was last true.

Capture the first program name after execution becomes ACTIVE:

variables:
first-active:
- source: execution == "ACTIVE"
- rising-edge
first-program:
- source: program_name
- latch-value:
reset: first-active

Latch a state machine with a reset value and change detection:

variables:
current-state:
- state:
- TR1: trigger-1
- TR2: trigger-2
- TR3: trigger-3
- TR4: trigger-4
- IDLE: true
- latch-value:
reset: trigger-reset
reset-value: IDLE
latch-on-change: true

7.10 Data Transformation Operations

resample

Changes update rate of a signal.

variables:
slow-temp:
- source: temperature
- resample: 5 # Only update every 5 seconds

hash

Generates a hash of the input value. Useful for change detection without storing full value.

variables:
config-hash:
- source: configuration-data
- hash # Generates hash for comparison

7.11 Array Processing Operations

map

Applies operations to each element in an array.

variables:
alarm-messages:
- source: alarm-codes
- map:
- expression: "'Code: ' + this"

Apply multiple operations to array elements:

variables:
processed-temps:
- source: temp-array
- map:
- expression: (this * 9 / 5) + 32 # Convert each to F
- expression: round(this, 1) # Round each value

8. Comprehensive Expression Reference

Expressions are formulas that calculate values using operators, functions, and identifiers. They can be used in source, expression, state conditions, data items, and many operation parameters.

8.1 Expression Syntax

Basic Structure:

# Simple expression
(temp-c * 9 / 5) + 32

# With identifiers
spindle-1-rpm + spindle-2-rpm

# Conditional (ternary)
mode == 'AUTO' ? 100 : 50

Using this Keyword: Within variable chains, this refers to the previous step's result:

variables:
adjusted-temp:
- source: temp-raw
- expression: this * 0.1 # 'this' = temp-raw value
- expression: (this * 9 / 5) + 32 # 'this' = scaled value

Property Access:

# Dot notation
oil.temperature

# Bracket notation (for special characters)
system['X.01'].message

# Nested properties
machine.spindle.speed

8.2 Mathematical Functions

Arithmetic Functions

abs(x) — Absolute value.

variables:
magnitude:
- source: abs(error-value)

round(x, n) — Round to the nearest integer or n decimal places.

variables:
display-temp:
- source: round(temperature, 2) # 72.456 → 72.46

floor(x) — Round down to next integer.

variables:
whole-parts:
- source: floor(part-count) # 45.9 → 45

ceil(x) — Round up to next integer.

variables:
boxes-needed:
- source: ceil(parts / box-capacity) # 101 / 25 → 5 boxes

sqrt(x) — Square root.

variables:
rms-current:
- source: sqrt((i1^2 + i2^2 + i3^2) / 3)

log(x, base) — Logarithm of x in the given base. Use log(x, 10) for base-10, log(x, 2.718) to approximate natural log.

variables:
decibels:
- source: 20 * log(voltage, 10)

Note: For exponentiation, use the ^ operator (e.g., radius ^ 2), not a pow() function.

Statistics Functions

These functions accept any number of arguments (variables, data sources, or constants).

FunctionDescription
min(a, b, ...)Minimum value
max(a, b, ...)Maximum value
sum(a, b, ...)Sum of values
mean(a, b, ...)Mean (average)
median(a, b, ...)Median value
variance(a, b, ...)Variance
std(a, b, ...)Standard deviation
prod(a, b, ...)Product of values
mode(a, b, ...)Mode (most common value)
variables:
peak-speed:
- source: max(spindle-1, spindle-2, spindle-3)

avg-zone-temp:
- source: mean(zone1-temp, zone2-temp, zone3-temp)

Trigonometric Functions

All trig functions use radians.

FunctionDescription
sin(x)Sine
cos(x)Cosine
tan(x)Tangent
sec(x)Secant
csc(x)Cosecant
cot(x)Cotangent
asin(x)Inverse sine
acos(x)Inverse cosine
atan(x)Inverse tangent
asec(x)Inverse secant
acsc(x)Inverse cosecant
acot(x)Inverse cotangent
variables:
wave:
- source: sin(angle * 3.14159 / 180) # Convert degrees to radians first

8.3 String Functions

Match Functions

startsWith(x, term) — True if string x starts with term (case-sensitive).

variables:
is-error-msg:
- source: startsWith(message, "EX")

endsWith(x, term) — True if string x ends with term (case-sensitive).

variables:
is-nc-file:
- source: endsWith(program-name, ".NC")

contains(x, term) — True if string x contains term anywhere (case-sensitive).

variables:
has-op:
- source: contains(program-comment, "OP")

match(x, pattern) / match(x, pattern, group) — Returns the matching substring for the given regex pattern in string x. If a group number is provided, returns only that capture group. Returns empty string if no match.

variables:
op-code:
- source: match(program-comment, 'M(\d+)', 1) # Captures digits after M

test(x, pattern) — True if string x contains a match for pattern.

variables:
is-setup-program:
- source: test(program, 'SETUP|WARMUP')

Modify Functions

replace(x, pattern, replacement) — Replaces all substrings in x matching the regex pattern with replacement. Supports backreferences $1, $2, etc.

variables:
clean-name:
- source: replace(program-name, '_', '-')

extracted:
- source: replace(alarm-msg, 'M(\d+)', '$1') # Keep only the digits

lpad(x, length, c) — Pads string x on the left with character c to reach length characters.

variables:
padded-id:
- source: lpad(part-id, 8, '0') # "123" → "00000123"

rpad(x, length, c) — Pads string x on the right with character c to reach length characters.

variables:
padded-name:
- source: rpad(program-name, 10, ' ')

Misc Functions

size(x) — Length of string x, or number of elements if x is an array.

variables:
name-length:
- source: size(program-name)

asc(x) — ASCII character for the number x. If x is an array of numbers, returns the corresponding ASCII string.

variables:
char-string:
- source: asc([ch1, ch2, ch3, ch4])

ord(x) / ord(x, index) — ASCII ordinal value of the first character of string x, or the character at index.

variables:
first-char-code:
- source: ord(program-name)

8.4 Utility Functions

flag(x, index) — Reads one bit from a bit-flag value x at 0-based index, returning true or false. If x is an array, reinterprets the element at index as a boolean.

variables:
door-open:
- source: flag(status-register, 3) # Read bit 3 of status register

# Equivalent to:
door-open-manual:
- source: (status-register & (1 << 3)) != 0

md5(x) — MD5 hash of string x.

variables:
config-fingerprint:
- source: md5(configuration-string)

Note: For conditional logic, use the ? : ternary operator documented in section 6 (e.g., temperature > 100 ? "HOT" : "OK"). There is no if() function.


8.6 Modbus-Specific Examples

Using expressions with Modbus register values from Sealevel devices:

Temperature Scaling:

registers:
temp-raw:
address: 40001
type: int16

variables:
temperature-f:
- source: (temp-raw * 0.1 * 9 / 5) + 32 # Scale and convert C to F

Combine Multiple Registers:

registers:
status-1:
address: 40010
type: uint16
status-2:
address: 40011
type: uint16

variables:
system-healthy:
- source: (status-1 & 0x01) and (status-2 & 0x02) # Bitwise checks

Calculate Totals:

registers:
good-parts:
address: 40020
type: uint32
reject-parts:
address: 40022
type: uint32

variables:
total-parts:
- source: good-parts + reject-parts

quality-rate:
- source: total-parts > 0 ? (good-parts / total-parts) * 100 : 0

9. Generators

Generators are self-contained data sources that produce values independently of the primary data source. There are three generator types: cron, counter, and datetime.

For full reference, see the MachineMetrics Adapter Script Generators documentation.

9.1 cron Generator

The cron generator outputs a boolean value — true while the schedule is active, false otherwise. The duration option controls how long the value stays true after the cron fires.

Syntax:

generators:
identifier:
type: cron
cron: "cron expression"
duration: interval
timezone: "TZ identifier"
  • cron (required) — A valid cron expression (5–7 fields). Must be quoted.
  • duration (required) — How long the generator stays true after firing (e.g., 8h, 30m).
  • timezone (optional) — TZ identifier such as America/New_York. If omitted, UTC is used.

Cron expression field order:

*  *  *  *  *  *  *
| | | | | | |
| | | | | | └── Year (optional)
| | | | | └───── Day of Week (0–7, SUN–SAT; 0 and 7 are Sunday)
| | | | └──────── Month (1–12 or JAN–DEC)
| | | └─────────── Day of Month (1–31)
| | └────────────── Hour (0–23)
| └───────────────── Minute (0–59)
└──────────────────── Second (optional, 0–59)

First Shift (6 AM–2 PM weekdays, Eastern time):

generators:
first_shift:
type: cron
cron: "0 6 * * 1-5"
timezone: America/New_York
duration: 8h

Daily reset at 7 AM:

generators:
day-start:
type: cron
cron: "0 7 * * *"
duration: 1m

Using a cron generator to reset a counter at each shift start:

generators:
shift-reset:
type: cron
cron: "0 7,15,23 * * *" # 7AM, 3PM, 11PM shift starts
duration: 1m

variables:
shift-part-count:
- source: part-complete
- count:
reset: shift-reset

9.2 counter Generator

The counter generator continuously increments a numeric value by 1 at a fixed interval. It is useful as a stable clock for adapter scripts — particularly for device types that provide data intermittently.

Syntax:

generators:
identifier:
type: counter
interval: interval
  • interval (required) — How often the counter increments (e.g., 1s, 5s).

Runtime clock (increments every second):

generators:
runtime:
type: counter
interval: 1s

9.3 datetime Generator

The datetime generator periodically updates a value with a formatted date/time string.

Syntax:

generators:
identifier:
type: datetime
format: "format string"
interval: interval
timezone: "TZ identifier"
  • format (optional) — Date/time format tokens. Defaults to YYYY-MM-DDTHH:mm:ss (ISO 8601).
  • interval (optional) — How often the value updates. Defaults to every second.
  • timezone (optional) — TZ identifier such as America/New_York. Defaults to UTC.
  • utc — Deprecated. Use timezone instead.

Common format tokens:

TokenOutputDescription
YYYY2026Four-digit year
MM03Month, 2-digits
DD27Day of month, 2-digits
HH14Hour (24-hour), 2-digits
mm05Minute, 2-digits
ss00Second, 2-digits
X1360013296Unix timestamp
x1360013296123Unix millisecond timestamp

Unix timestamp updated every 5 seconds:

generators:
timestamp:
type: datetime
format: X
interval: 5s

ISO date string in local timezone:

generators:
local-time:
type: datetime
format: "YYYY-MM-DDTHH:mm:ss"
interval: 1s
timezone: America/Chicago

10. Data Items (Output)

The data-items block declares which identifiers are sent to MachineMetrics.

Basic List

data-items:
- part-count
- execution
- spindle-speed
- program

Data Item Expressions

Calculate values inline:

data-items:
- part-count
- part-material
- system-ready: oven-temp > 40 # Boolean expression
- temp-display: round(temperature, 1) # Calculated value

Renaming Outputs

To send data with a different name than the variable:

# Option 1: Define a new variable
variables:
machine-rpm: spindle-speed

# Option 2: Use expression syntax
data-items:
- machine-rpm: spindle-speed

11. Conditions (Alarms)

Conditions represent alarms with codes, messages, and severity levels.

Simple Condition

conditions:
coolant-low:
message: Coolant level is low
value:
FAULT: coolant-level < 1.5

Multi-Level Condition

conditions:
coolant-low:
- code: coolant-critical
message: Coolant level critical - machine will stop
value:
FAULT: coolant-level < 0.5
- code: coolant-warning
message: Coolant level low - refill soon
value:
WARNING: coolant-level < 1.5 and coolant-level >= 0.5

Dynamic Codes and Messages

Use string expressions for dynamic values:

tags:
fault_code: HMI1.StaData.FaultNumber
fault_msg: HMI1.StaData.FaultDescription

conditions:
system:
- code: ${fault_code}
message: ${fault_msg}
value:
FAULT: fault_ind > 0

12. Complete Examples

Example 0: Transform Adapter - Extract Operation from Program Comment

This example shows a transform adapter that pulls data from a parent FANUC FOCAS adapter and extracts the operation number from the program comment.

Parent Adapter: FANUC FOCAS (collecting program_comment like "O1234(OP10-ROUGH-MILL)")
Transform Adapter: Extracts "OP10-ROUGH-MILL" as the operation data item

version: 2

# INPUT: Pull data items from parent FOCAS adapter
declare-keys:
- program_comment

# TRANSFORMATION: Extract operation from program comment using regex
variables:
operation:
- source: program_comment
- pattern-match:
pattern: /\(([^)]+)\)/
group: 1

# OUTPUT: Send extracted operation to MachineMetrics
data-items:
- operation

How It Works:

  1. Parent FOCAS adapter collects program_comment from the CNC controller
  2. Transform adapter declares it needs program_comment via declare-keys
  3. Regex pattern /\(([^)]+)\)/ matches text inside parentheses
  4. group: 1 extracts the captured group (the text between parentheses)
  5. Result is output as the operation data item

Example Transformations:

  • Input: "O1234(OP10-ROUGH-MILL)" → Output: "OP10-ROUGH-MILL"
  • Input: "O5678(OP20-FINISH)" → Output: "OP20-FINISH"
  • Input: "O9999(INSPECTION)" → Output: "INSPECTION"

When to Use Transform Adapters:

  • Extract structured data from text fields (program names, comments)
  • Parse part numbers, operations, or work orders
  • Convert data formats (units, encodings)
  • Derive metrics from existing data items
  • Clean or normalize data before sending to MachineMetrics

Example 1: Part Counter with Execution State

This example shows a direct connectivity adapter using EtherNet/IP.

version: 2

# Input - EtherNet/IP
slot: 1
scan-interval: 0.25
tags:
mode_auto: Module10.ModeAuto
cycle_complete: Module10.CycleComplete
fault_ind: Module10.FaultInd
spindle_on: Module10.SpindleRunning

# Transformation
variables:
execution:
- state:
- ACTIVE: mode_auto and spindle_on and fault_ind == 0
- INTERRUPTED: fault_ind > 0
- READY: true

part-count:
- source: cycle_complete
- rising-edge
- count

# Output
data-items:
- execution
- part-count
- spindle_on

conditions:
machine-fault:
message: Machine is in fault state
value:
FAULT: fault_ind > 0

Example 2: Modbus Temperature Monitoring

version: 2

# Input - Modbus
unit-id: 1
byte-order: big
registers:
temp-raw:
address: 40001
type: int16
setpoint:
address: 40002
type: int16

# Transformation
variables:
temperature:
- source: temp-raw / 10 # Scale raw value

temp-status:
- state:
- HIGH: temperature > setpoint + 10
- LOW: temperature < setpoint - 10
- NORMAL: true

# Output
data-items:
- temperature
- setpoint
- temp-status

conditions:
temp-alarm:
- code: over-temp
message: Temperature exceeds setpoint by more than 10 degrees
value:
FAULT: temperature > setpoint + 10
- code: under-temp
message: Temperature below setpoint by more than 10 degrees
value:
WARNING: temperature < setpoint - 10

Example 3: OPC-UA with Calculated OEE Component

version: 2

# Input - OPC-UA
tags:
actual-speed: ns=2;s=Spindle.ActualSpeed
rated-speed: ns=2;s=Spindle.RatedSpeed
good-parts: ns=2;s=Production.GoodParts
total-parts: ns=2;s=Production.TotalParts

# Transformation
variables:
performance:
- source: (actual-speed / rated-speed) * 100
- expression: min(this, 100) # Cap at 100%

quality:
- source: total-parts > 0 ? (good-parts / total-parts) * 100 : 0

# Output
data-items:
- actual-speed
- performance
- quality
- good-parts
- total-parts

Example 4: Transform Adapter with Passthrough and Filtering

This example shows a transform adapter that uses mtconnect-passthrough to pass all parent FOCAS data through, while also adding transformed data and filtering out specific items.

Scenario: Parent FOCAS adapter provides 50+ data items. We want to:

  1. Pass all FOCAS data through unchanged
  2. Extract operation and part number from program comment
  3. Block internal debug flags from reaching MachineMetrics
version: 2

# Pass all parent FOCAS data through automatically
mtconnect-passthrough: true

# Pull specific items for transformation
declare-keys:
- program_comment

# Block these items from parent adapter
deny-keys:
- debug_mode
- internal_flag
- raw_alarm_buffer

# Transform program comment into structured data
variables:
# Extract operation: O1234(OP10-ROUGH) → OP10-ROUGH
operation:
- source: program_comment
- pattern-match:
pattern: /\(([^)]+)\)/
group: 1

# Extract part number: O1234(OP10-ROUGH) → 1234
part_number:
- source: program_comment
- pattern-match:
pattern: /O(\d+)/
group: 1

# Output transformed data items
data-items:
- operation
- part_number

Result:

  • All 50+ FOCAS data items (spindle_speed, tool_number, execution, etc.) pass through
  • New data items operation and part_number are added
  • Items in deny-keys are filtered out and don't reach MachineMetrics
  • Total: 50+ original items + 2 new items - 3 blocked items

13. Troubleshooting

Common Issues

ProblemCauseSolution
Data shows UNAVAILABLESource disconnected or expression errorCheck connectivity; validate expressions
Double-counting partsMultiple rising edges per cycleAdd debounce or adjust threshold
State not changingExpression always falseTest each condition independently
Wrong data typeString vs number mismatchUse explicit conversion functions

Debugging Tips

  1. Start simple — Get basic connectivity working before adding transforms
  2. Test incrementally — Add one variable at a time and verify output
  3. Check Data Mapping — Ensure transformed items are properly mapped in MachineMetrics
  4. Use Diagnostics — View raw data items in the machine's Diagnostics tab

YAML Syntax Notes

  • Indent with spaces (not tabs)
  • Use quotes around strings containing colons followed by spaces
  • Boolean values: true / false (lowercase)
  • Comments start with #


Additional Resources