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

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

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.

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, but data flows bottom to top (last operation = final value).

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:

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

Property Access

Access object properties with dot notation or brackets:

# Dot notation
this.temperature

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

# Chained access
oil.system.temperature

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: 500ms # Ignore bounces < 500ms

on-delay

Delays true signal, passes false immediately.

variables:
confirmed-running:
- source: spindle-on
- on-delay: 2s # Must be on for 2s 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: 5s # Holds ACTIVE state for 5s 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: 3s # Keeps "running" for 3s after motion stops

Modbus Example - Stabilize Noisy Execution Signal:

registers:
exec_status:
address: 40010
type: uint16

variables:
execution:
- source: exec_status > 0
- off-delay: 10s # Hold execution ON for 10s 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: 60s # 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

Holds last value when input becomes unavailable.

variables:
last-known-speed:
- source: spindle-speed
- latch-value # Retains last good value if sensor fails

7.10 Data Transformation Operations

resample

Changes update rate of a signal.

variables:
slow-temp:
- source: temperature
- resample: 5s # 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

abs(x)

Returns absolute value.

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

round(x, precision)

Rounds to specified decimal places.

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

floor(x)

Rounds down to nearest integer.

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

ceil(x)

Rounds up to nearest integer.

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

min(a, b, ...)

Returns smallest value.

variables:
lowest-temp:
- source: min(zone1-temp, zone2-temp, zone3-temp)

max(a, b, ...)

Returns largest value.

variables:
peak-speed:
- source: max(spindle-1, spindle-2)

clamp(value, min, max)

Constrains value within range.

variables:
limited-speed:
- source: clamp(requested-speed, 0, 3000) # Keep between 0-3000

pow(base, exponent)

Raises to power.

variables:
area:
- source: pow(radius, 2) * 3.14159 # πr²

sqrt(x)

Square root.

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

exp(x)

Exponential (e^x).

variables:
decay:
- source: exp(-time / tau)

ln(x)

Natural logarithm.

variables:
log-scale:
- source: ln(signal-strength)

log10(x)

Base-10 logarithm.

variables:
decibels:
- source: 20 * log10(voltage)

sin(x), cos(x), tan(x)

Trigonometric functions (x in radians).

variables:
wave:
- source: sin(angle * 3.14159 / 180) # Convert degrees to radians

asin(x), acos(x), atan(x)

Inverse trigonometric functions.

variables:
angle:
- source: atan(y / x) * 180 / 3.14159 # Radians to degrees

atan2(y, x)

Two-argument arctangent (handles quadrants correctly).

variables:
rotation-angle:
- source: atan2(y-pos, x-pos)

8.3 String Functions

concat(string1, string2, ...)

Concatenates strings.

variables:
full-name:
- source: concat(part-prefix, "-", part-number) # "PART-12345"

substring(string, start, length)

Extracts portion of string.

variables:
part-prefix:
- source: substring(part-id, 0, 4) # First 4 characters

Finds position of substring (-1 if not found).

variables:
has-op:
- source: indexOf(program-comment, "OP") >= 0 # true if contains "OP"

length(string)

Returns string length.

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

toLowerCase(string)

Converts to lowercase.

variables:
normalized:
- source: toLowerCase(operator-input)

toUpperCase(string)

Converts to uppercase.

variables:
uppercase-code:
- source: toUpperCase(part-code)

trim(string)

Removes leading/trailing whitespace.

variables:
clean-name:
- source: trim(program-name)

replace(string, search, replacement)

Replaces first occurrence.

variables:
fixed-name:
- source: replace(program, "_", "-")

split(string, delimiter)

Splits string into array.

variables:
parts-array:
- source: split(part-list, ",") # "A,B,C" → ["A", "B", "C"]

join(array, delimiter)

Joins array into string.

variables:
combined:
- source: join(parts-array, " | ") # ["A", "B"] → "A | B"

8.4 Utility Functions

if(condition, true-value, false-value)

Conditional function (same as ternary operator).

variables:
status:
- source: if(temperature > 100, "HOT", "OK")

isNaN(value)

Checks if value is Not-a-Number.

variables:
is-invalid:
- source: isNaN(sensor-reading)

parseFloat(string)

Converts string to number.

variables:
numeric-value:
- source: parseFloat(string-reading) # "42.5" → 42.5

parseInt(string, radix)

Converts string to integer.

variables:
int-value:
- source: parseInt(count-string, 10) # "42" → 42

toString(value)

Converts value to string.

variables:
string-value:
- source: toString(part-count) # 42 → "42"

typeof(value)

Returns type of value as string.

variables:
data-type:
- source: typeof(sensor-value) # "number", "string", "boolean", etc.

defined(identifier)

Checks if identifier is defined (not unavailable).

variables:
has-data:
- source: defined(optional-sensor) # true if sensor is available

8.5 Date and Time Functions

now()

Returns current Unix timestamp (seconds since epoch).

variables:
timestamp:
- source: now() # Current time in seconds

date()

Returns current date/time as ISO string.

variables:
current-time:
- source: date() # "2026-01-27T10:30:00Z"

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 create data streams based on patterns or time schedules. Useful for simulating sensors, creating periodic signals, or scheduling operations.

9.1 CRON Generator

Creates signals based on CRON schedule expressions. Outputs true when schedule matches, false otherwise.

Basic CRON Syntax:

*    *    *    *    *    *
│ │ │ │ │ │
│ │ │ │ │ └─── Day of Week (0-7, 0 and 7 are Sunday)
│ │ │ │ └──────── Month (1-12)
│ │ │ └───────────── Day of Month (1-31)
│ │ └────────────────── Hour (0-23)
│ └─────────────────────── Minute (0-59)
└──────────────────────────── Second (0-59)

Daily Shift Start:

generators:
shift-start:
type: cron
schedule: "0 0 7 * * *" # Every day at 7:00:00 AM

Hourly Production Report:

generators:
hourly-trigger:
type: cron
schedule: "0 0 * * * *" # Top of every hour

Weekend Check:

generators:
is-weekend:
type: cron
schedule: "* * * * * 0,6" # True on Saturday (6) and Sunday (0)

Every 15 Minutes:

generators:
quarter-hour:
type: cron
schedule: "0 */15 * * * *" # :00, :15, :30, :45 of each hour

Weekday Working Hours Only (7 AM - 5 PM, Mon-Fri):

generators:
working-hours:
type: cron
schedule: "0 0 7-17 * * 1-5" # 7 AM-5 PM, Monday-Friday

First Day of Month:

generators:
month-start:
type: cron
schedule: "0 0 0 1 * *" # Midnight on 1st of each month

Using with Variables:

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

variables:
shift-part-count:
- source: part-complete
- count:
reset: shift-reset # Reset counter at each shift start

9.2 Periodic Generator

Creates periodic signals at fixed intervals.

Every 5 Seconds:

generators:
heartbeat:
type: periodic
interval: 5s

Every Minute:

generators:
minute-tick:
type: periodic
interval: 60s

Using with Resample:

generators:
sample-trigger:
type: periodic
interval: 10s

variables:
avg-temp-sampled:
- source: temperature
- resample: sample-trigger # Only update every 10s

9.3 Generator Use Cases

Daily Production Reset:

generators:
day-start:
type: cron
schedule: "0 0 6 * * *" # 6 AM daily

variables:
daily-parts:
- source: part-complete
- count:
reset: day-start

Periodic Status Report:

generators:
report-time:
type: cron
schedule: "0 0 8,12,16,20 * * *" # 8 AM, 12 PM, 4 PM, 8 PM

data-items:
- hourly-report: report-time # Sends update at scheduled times

Weekend vs Weekday Schedules:

generators:
is-weekday:
type: cron
schedule: "* * * * * 1-5" # Monday-Friday

is-weekend:
type: cron
schedule: "* * * * * 0,6" # Saturday-Sunday

variables:
target-rate:
- source: is-weekday ? 100 : 50 # Different targets for weekday/weekend

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