Transform Adapter Scripts
- What Are Adapter Scripts?
- Adapter Script Structure
- Data Types
- Declaring Input Keys
- Variables
- Expressions and Operators
- Transform Operations Reference
- Comprehensive Expression Reference
- Generators
- Data Items (Output)
- Conditions (Alarms)
- Complete Examples
- Troubleshooting
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:
- Configure Connectivity — Specify which tags, registers, or data points to read from a machine
- Transform Data — Manipulate, combine, or synthesize data into new signals
When to Use Adapter Scripts
| Use Case | Example |
|---|---|
| Combine multiple signals into one state | Spindle + coolant + door → execution state |
| Count parts from a pulsing signal | Rising edge detection on analog input |
| Convert sensor values | Temperature C → F, voltage → pressure |
| Create custom alarms/conditions | Low coolant warning from analog level |
| Filter or clean data | Ignore warmup programs, remove noise |
| Derive calculated metrics | Efficiency from multiple inputs |
2. Adapter Script Structure
Two Types of Adapter Scripts
MachineMetrics uses two types of adapter scripts:
- Direct Connectivity Adapters — Connect directly to machine protocols (FANUC FOCAS, Modbus, OPC-UA, EtherNet/IP)
- 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 Type | Input Section | Purpose |
|---|---|---|
| Direct Connectivity | tags, registers, coils, pins | Read data directly from machine protocols |
| Transform | declare-keys | Pull existing data items from parent adapter |
| Both | variables | Process and combine data |
| Both | data-items, conditions | Send data to MachineMetrics |
3. Data Types
Understanding data types is essential for writing effective adapter scripts.
Core Data Types
| Type | Description | Example |
|---|---|---|
| Boolean | True/false values | true, false, digital I/O signals |
| Number | Integers or decimals | 42, 3.14, analog readings |
| String | Text values | "ACTIVE", "Program123" |
| Array | List of values | [1, 2, 3], register arrays |
| Object | Named 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
UNAVAILABLEis 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:
- Parent adapter (e.g., FANUC FOCAS) collects data items like
program_comment,tool_number,spindle_override - Transform adapter declares which data items it needs using
declare-keys - Transform adapter processes those data items through
variables - 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:
- Parent adapter collects many data items (e.g., 50+ from FANUC FOCAS)
- Transform adapter sets
mtconnect-passthrough: true - All parent data items are automatically passed to MachineMetrics
- Transform adapter only needs to
declare-keysfor items it wants to transform - 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:
| Operation | Purpose | Example |
|---|---|---|
source | Reference another identifier or expression | source: spindle-speed |
expression | Calculate from multiple sources | expression: (temp * 9/5) + 32 |
state | Map conditions to state values | See 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
| Category | Operators | Example |
|---|---|---|
| Arithmetic | +, -, *, /, % (modulus), ^ (power) | (temp * 9/5) + 32 |
| Boolean | and, or, not | spindle-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
| Function | Purpose | Example |
|---|---|---|
abs(x) | Absolute value | abs(spindle-speed) |
round(x, n) | Round to n decimals | round(temp, 2) |
floor(x) | Round down | floor(count) |
ceil(x) | Round up | ceil(count) |
min(a, b) | Smaller value | min(speed1, speed2) |
max(a, b) | Larger value | max(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
indexOf(string, search)
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:
- Parent FOCAS adapter collects
program_commentfrom the CNC controller - Transform adapter declares it needs
program_commentviadeclare-keys - Regex pattern
/\(([^)]+)\)/matches text inside parentheses group: 1extracts the captured group (the text between parentheses)- Result is output as the
operationdata 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:
- Pass all FOCAS data through unchanged
- Extract operation and part number from program comment
- 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
operationandpart_numberare added - Items in
deny-keysare filtered out and don't reach MachineMetrics - Total: 50+ original items + 2 new items - 3 blocked items
13. Troubleshooting
Common Issues
| Problem | Cause | Solution |
|---|---|---|
| Data shows UNAVAILABLE | Source disconnected or expression error | Check connectivity; validate expressions |
| Double-counting parts | Multiple rising edges per cycle | Add debounce or adjust threshold |
| State not changing | Expression always false | Test each condition independently |
| Wrong data type | String vs number mismatch | Use explicit conversion functions |
Debugging Tips
- Start simple — Get basic connectivity working before adding transforms
- Test incrementally — Add one variable at a time and verify output
- Check Data Mapping — Ensure transformed items are properly mapped in MachineMetrics
- 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
#
Related Articles
- Machine Settings Guide — Configure data collection and mapping
- Connectivity Overview — Choose the right connection method
- OPC-UA Connectivity Guide — OPC-UA specific configuration
- Modbus TCP Connectivity Guide — Modbus register configuration
Additional Resources
- MachineMetrics Developer Documentation — Complete adapter script reference
- Adapter Script Operations Reference — All available operations
- Expression Functions — Math, string, and utility functions