Skip to main content

Create States with Scripting

We'll use scripts to create and link a large set of downtime states. You define the states as a list of dictionaries, create any missing state categories, create the states, and link them to an asset with a state code for each asset/state link.

The order matters: a state category must exist before you can create a state in it, and a state must exist before you can link it to an asset. The state code is not part of the state itself; it is assigned when the state is linked to an asset. Machine 1's states get fixed codes (61 to 114), because the CoreForge simulator sends those codes for Machine 1. Manual Pack 1 has no simulator, so its new links are numbered from the highest state code already linked to it, which avoids conflicts.

Create the asset states from script
  1. Copy the following script to the Script Console in the Designer and run it. It creates the state categories and states for Machine 1 and Manual Pack 1 and links them to both assets. It's safe to run again: anything that already exists is left alone.
View the generateStates script
generateStates.py
"""
Create the downtime and running states for Machine 1 and Manual Pack 1, and link them.

Run in the Ignition Designer Script Console. States in Kanoa are built in three layers,
and each has to exist before the next:

1. State categories, e.g. Mechanical\\Spindle. A category path can be several
levels deep; any missing level is created, parent first.
2. States, e.g. 'Spindle Overload' in Mechanical\\Spindle, with a state type
(Running, Planned Downtime, Unplanned Downtime) and a colour. States belong to
the whole enterprise, not to one asset, so a state listed for both assets
(e.g. 'Unexpected Stop') is created once and linked to both.
3. Asset-state links. Linking a state to an asset is what makes it available on
that asset, and the link holds the state code: the number the PLC (here, the
CoreForge simulator) sends to say which state the asset is in.

State codes: Machine 1's states have fixed codes (61 to 114) because the CoreForge
simulator sends those codes for Machine 1. Manual Pack 1 has no simulator, so its
states are numbered from the highest code already linked to it, skipping any code
in use. Add 'stateCode': n to a definition to pin a code.

Safe to run again: existing categories and states are reused, states already
linked to an asset are left alone, and only what's missing is created or linked.
Nothing is deleted or changed.
"""
import sys

userId = system.kanoa.security.getIDPUserId({'userName': 'SYSTEM'})

# Each asset and the states to link to it: state code (optional), name, category path, state type and colour
ASSETS = [
{
'assetPath': 'CoreForge\\Philadelphia\\Machining\\Machine 1',
'states': [
{'stateCode': 61, 'stateName': 'Part replacement', 'stateCategory': 'Mechanical', 'stateType': 'Unplanned Downtime', 'stateColor': '#ba1a1a'},
{'stateCode': 62, 'stateName': 'Minor Fault', 'stateCategory': 'Generic', 'stateType': 'Unplanned Downtime', 'stateColor': '#FF8A8A'},
{'stateCode': 63, 'stateName': 'Major Fault', 'stateCategory': 'Generic', 'stateType': 'Unplanned Downtime', 'stateColor': '#ba1a1a'},
{'stateCode': 64, 'stateName': 'Material Defect', 'stateCategory': 'Process Failure', 'stateType': 'Unplanned Downtime', 'stateColor': '#ba1a1a'},
{'stateCode': 65, 'stateName': 'Setup Failure', 'stateCategory': 'Process Failure', 'stateType': 'Unplanned Downtime', 'stateColor': '#ba1a1a'},
{'stateCode': 66, 'stateName': 'Utility Outage', 'stateCategory': 'Process Failure', 'stateType': 'Unplanned Downtime', 'stateColor': '#ba1a1a'},
{'stateCode': 67, 'stateName': 'Automatic Cycle Running', 'stateCategory': 'Production', 'stateType': 'Running', 'stateColor': '#616161'},
{'stateCode': 68, 'stateName': 'Manual Operation', 'stateCategory': 'Production\\Manual', 'stateType': 'Running', 'stateColor': '#616161'},
{'stateCode': 69, 'stateName': 'Probe Measurement Cycle', 'stateCategory': 'Inspection', 'stateType': 'Running', 'stateColor': '#616161'},
{'stateCode': 70, 'stateName': 'Tool Changeover', 'stateCategory': 'Tooling', 'stateType': 'Planned Downtime', 'stateColor': '#616161'},
{'stateCode': 71, 'stateName': 'Fixture Changeover', 'stateCategory': 'Setup', 'stateType': 'Planned Downtime', 'stateColor': '#616161'},
{'stateCode': 72, 'stateName': 'Preventive Maintenance', 'stateCategory': 'Maintenance', 'stateType': 'Planned Downtime', 'stateColor': '#616161'},
{'stateCode': 73, 'stateName': 'Emergency Stop Activated', 'stateCategory': 'Safety', 'stateType': 'Unplanned Downtime', 'stateColor': '#ba1a1a'},
{'stateCode': 74, 'stateName': 'Door Open Alarm', 'stateCategory': 'Safety', 'stateType': 'Unplanned Downtime', 'stateColor': '#D32F2F'},
{'stateCode': 75, 'stateName': 'Light Curtain Fault', 'stateCategory': 'Safety', 'stateType': 'Unplanned Downtime', 'stateColor': '#E53935'},
{'stateCode': 76, 'stateName': 'Spindle Overload', 'stateCategory': 'Mechanical\\Spindle', 'stateType': 'Unplanned Downtime', 'stateColor': '#616161'},
{'stateCode': 77, 'stateName': 'Spindle Bearing Failure', 'stateCategory': 'Mechanical\\Spindle', 'stateType': 'Unplanned Downtime', 'stateColor': '#7B1FA2'},
{'stateCode': 78, 'stateName': 'Spindle Orientation Fault', 'stateCategory': 'Mechanical\\Spindle', 'stateType': 'Unplanned Downtime', 'stateColor': '#9C27B0'},
{'stateCode': 79, 'stateName': 'Axis Drive Fault', 'stateCategory': 'Mechanical\\Axis', 'stateType': 'Unplanned Downtime', 'stateColor': '#616161'},
{'stateCode': 80, 'stateName': 'Axis Overtravel', 'stateCategory': 'Mechanical\\Axis', 'stateType': 'Unplanned Downtime', 'stateColor': '#7E57C2'},
{'stateCode': 81, 'stateName': 'Ballscrew Fault', 'stateCategory': 'Mechanical\\Axis', 'stateType': 'Unplanned Downtime', 'stateColor': '#5E35B1'},
{'stateCode': 82, 'stateName': 'Linear Guide Fault', 'stateCategory': 'Mechanical\\Axis', 'stateType': 'Unplanned Downtime', 'stateColor': '#512DA8'},
{'stateCode': 83, 'stateName': 'Chuck Clamp Failure', 'stateCategory': 'Mechanical\\Workholding', 'stateType': 'Unplanned Downtime', 'stateColor': '#616161'},
{'stateCode': 84, 'stateName': 'Fixture Clamp Fault', 'stateCategory': 'Mechanical\\Workholding', 'stateType': 'Unplanned Downtime', 'stateColor': '#303F9F'},
{'stateCode': 85, 'stateName': 'Part Detection Failure', 'stateCategory': 'Mechanical\\Workholding', 'stateType': 'Unplanned Downtime', 'stateColor': '#283593'},
{'stateCode': 86, 'stateName': 'Tool Breakage Detected', 'stateCategory': 'Tooling', 'stateType': 'Unplanned Downtime', 'stateColor': '#616161'},
{'stateCode': 87, 'stateName': 'Tool Not Found', 'stateCategory': 'Tooling', 'stateType': 'Unplanned Downtime', 'stateColor': '#F57C00'},
{'stateCode': 88, 'stateName': 'Tool Changer Fault', 'stateCategory': 'Tooling', 'stateType': 'Unplanned Downtime', 'stateColor': '#FB8C00'},
{'stateCode': 89, 'stateName': 'Tool Life Exceeded', 'stateCategory': 'Tooling', 'stateType': 'Unplanned Downtime', 'stateColor': '#FFA000'},
{'stateCode': 90, 'stateName': 'Incorrect Tool Loaded', 'stateCategory': 'Tooling', 'stateType': 'Unplanned Downtime', 'stateColor': '#FF8F00'},
{'stateCode': 91, 'stateName': 'Servo Alarm', 'stateCategory': 'Controls\\Servo', 'stateType': 'Unplanned Downtime', 'stateColor': '#616161'},
{'stateCode': 92, 'stateName': 'Encoder Fault', 'stateCategory': 'Controls\\Feedback', 'stateType': 'Unplanned Downtime', 'stateColor': '#616161'},
{'stateCode': 93, 'stateName': 'PLC Communication Loss', 'stateCategory': 'Controls\\Communication', 'stateType': 'Unplanned Downtime', 'stateColor': '#616161'},
{'stateCode': 94, 'stateName': 'CNC Program Error', 'stateCategory': 'Controls\\Program', 'stateType': 'Unplanned Downtime', 'stateColor': '#616161'},
{'stateCode': 95, 'stateName': 'M-Code Timeout', 'stateCategory': 'Controls\\Program', 'stateType': 'Unplanned Downtime', 'stateColor': '#26C6DA'},
{'stateCode': 96, 'stateName': 'Software Crash', 'stateCategory': 'Controls\\Software', 'stateType': 'Unplanned Downtime', 'stateColor': '#616161'},
{'stateCode': 97, 'stateName': 'Power Failure', 'stateCategory': 'Electrical', 'stateType': 'Unplanned Downtime', 'stateColor': '#616161'},
{'stateCode': 98, 'stateName': 'Control Cabinet Overtemperature', 'stateCategory': 'Electrical\\Cabinet', 'stateType': 'Unplanned Downtime', 'stateColor': '#616161'},
{'stateCode': 99, 'stateName': 'Drive Amplifier Fault', 'stateCategory': 'Electrical\\Drive', 'stateType': 'Unplanned Downtime', 'stateColor': '#616161'},
{'stateCode': 100, 'stateName': 'Breaker Trip', 'stateCategory': 'Electrical\\Power', 'stateType': 'Unplanned Downtime', 'stateColor': '#616161'},
{'stateCode': 101, 'stateName': 'Low Air Pressure', 'stateCategory': 'Utilities\\Air', 'stateType': 'Unplanned Downtime', 'stateColor': '#616161'},
{'stateCode': 102, 'stateName': 'Hydraulic Pressure Fault', 'stateCategory': 'Utilities\\Hydraulic', 'stateType': 'Unplanned Downtime', 'stateColor': '#616161'},
{'stateCode': 103, 'stateName': 'Vacuum Failure', 'stateCategory': 'Utilities\\Vacuum', 'stateType': 'Unplanned Downtime', 'stateColor': '#616161'},
{'stateCode': 104, 'stateName': 'Coolant Pump Failure', 'stateCategory': 'Utilities\\Coolant', 'stateType': 'Unplanned Downtime', 'stateColor': '#616161'},
{'stateCode': 105, 'stateName': 'Low Coolant Level', 'stateCategory': 'Utilities\\Coolant', 'stateType': 'Unplanned Downtime', 'stateColor': '#78909C'},
{'stateCode': 106, 'stateName': 'Coolant Temperature High', 'stateCategory': 'Utilities\\Coolant', 'stateType': 'Unplanned Downtime', 'stateColor': '#90A4AE'},
{'stateCode': 107, 'stateName': 'Chip Conveyor Jam', 'stateCategory': 'Material Handling\\Chips', 'stateType': 'Unplanned Downtime', 'stateColor': '#616161'},
{'stateCode': 108, 'stateName': 'Chip Bin Full', 'stateCategory': 'Material Handling\\Chips', 'stateType': 'Unplanned Downtime', 'stateColor': '#6D4C41'},
{'stateCode': 109, 'stateName': 'Bar Feeder Fault', 'stateCategory': 'Material Handling\\Feed', 'stateType': 'Unplanned Downtime', 'stateColor': '#616161'},
{'stateCode': 110, 'stateName': 'Material Load Failure', 'stateCategory': 'Material Handling\\Feed', 'stateType': 'Unplanned Downtime', 'stateColor': '#4E342E'},
{'stateCode': 111, 'stateName': 'Network Disconnect', 'stateCategory': 'IT\\Network', 'stateType': 'Unplanned Downtime', 'stateColor': '#616161'},
{'stateCode': 112, 'stateName': 'MES Communication Loss', 'stateCategory': 'IT\\Integration', 'stateType': 'Unplanned Downtime', 'stateColor': '#616161'},
{'stateCode': 113, 'stateName': 'Unexpected Stop', 'stateCategory': 'Unknown', 'stateType': 'Unplanned Downtime', 'stateColor': '#757575'},
{'stateCode': 114, 'stateName': 'Alarm Requires Operator Reset', 'stateCategory': 'Unknown', 'stateType': 'Unplanned Downtime', 'stateColor': '#616161'}
]
},
{
'assetPath': 'CoreForge\\Philadelphia\\Finishing\\Manual Pack 1',
'states': [
{'stateName': 'Packaging Material Changeover', 'stateCategory': 'Changeover\\Packaging', 'stateType': 'Planned Downtime', 'stateColor': '#F9A825'},
{'stateName': 'Label Roll Changeover', 'stateCategory': 'Changeover\\Labeling', 'stateType': 'Planned Downtime', 'stateColor': '#FBC02D'},
{'stateName': 'Packing Station Cleaning', 'stateCategory': 'Sanitation', 'stateType': 'Planned Downtime', 'stateColor': '#FDD835'},
{'stateName': 'Missing Packaging Materials', 'stateCategory': 'Material\\Packaging', 'stateType': 'Unplanned Downtime', 'stateColor': '#EF6C00'},
{'stateName': 'Wrong Box Size Available', 'stateCategory': 'Material\\Packaging', 'stateType': 'Unplanned Downtime', 'stateColor': '#F57C00'},
{'stateName': 'Damaged Boxes', 'stateCategory': 'Material\\Packaging', 'stateType': 'Unplanned Downtime', 'stateColor': '#FB8C00'},
{'stateName': 'Missing Product Inserts', 'stateCategory': 'Material\\Documentation', 'stateType': 'Unplanned Downtime', 'stateColor': '#FFA000'},
{'stateName': 'Label Printer Offline', 'stateCategory': 'Equipment\\Labeling', 'stateType': 'Unplanned Downtime', 'stateColor': '#AD1457'},
{'stateName': 'Label Printer Jam', 'stateCategory': 'Equipment\\Labeling', 'stateType': 'Unplanned Downtime', 'stateColor': '#C2185B'},
{'stateName': 'Label Misprint', 'stateCategory': 'Equipment\\Labeling', 'stateType': 'Unplanned Downtime', 'stateColor': '#D81B60'},
{'stateName': 'Scanner Not Reading Barcode', 'stateCategory': 'Equipment\\Scanning', 'stateType': 'Unplanned Downtime', 'stateColor': '#E91E63'},
{'stateName': 'Scale Communication Loss', 'stateCategory': 'Equipment\\Scale', 'stateType': 'Unplanned Downtime', 'stateColor': '#00838F'},
{'stateName': 'Scale Out of Tolerance', 'stateCategory': 'Equipment\\Scale', 'stateType': 'Unplanned Downtime', 'stateColor': '#0097A7'},
{'stateName': 'Box Taper Fault', 'stateCategory': 'Equipment\\Sealing', 'stateType': 'Unplanned Downtime', 'stateColor': '#00ACC1'},
{'stateName': 'Tape Roll Empty', 'stateCategory': 'Equipment\\Sealing', 'stateType': 'Unplanned Downtime', 'stateColor': '#00BCD4'},
{'stateName': 'Product Count Mismatch', 'stateCategory': 'Quality\\Count', 'stateType': 'Unplanned Downtime', 'stateColor': '#6A1B9A'},
{'stateName': 'Incorrect Item at Pack Station', 'stateCategory': 'Quality\\Identification', 'stateType': 'Unplanned Downtime', 'stateColor': '#7E57C2'},
{'stateName': 'Incorrect Coating Variant', 'stateCategory': 'Quality\\Identification', 'stateType': 'Unplanned Downtime', 'stateColor': '#5E35B1'},
{'stateName': 'Damaged Parts Found', 'stateCategory': 'Quality\\Parts', 'stateType': 'Unplanned Downtime', 'stateColor': '#512DA8'},
{'stateName': 'Bad Coating Found', 'stateCategory': 'Quality\\Coating', 'stateType': 'Unplanned Downtime', 'stateColor': '#8E24AA'},
{'stateName': 'Work Instruction Missing', 'stateCategory': 'Operations\\Documentation', 'stateType': 'Unplanned Downtime', 'stateColor': '#3949AB'},
{'stateName': 'Order Information Mismatch', 'stateCategory': 'Operations\\Order', 'stateType': 'Unplanned Downtime', 'stateColor': '#303F9F'},
{'stateName': 'Awaiting QA Release', 'stateCategory': 'Operations\\Hold', 'stateType': 'Unplanned Downtime', 'stateColor': '#283593'},
{'stateName': 'Awaiting Material Movement', 'stateCategory': 'Operations\\Material Flow', 'stateType': 'Unplanned Downtime', 'stateColor': '#1A237E'},
{'stateName': 'MES Communication Loss', 'stateCategory': 'IT\\Integration', 'stateType': 'Unplanned Downtime', 'stateColor': '#0277BD'},
{'stateName': 'Barcode Lookup Failed', 'stateCategory': 'IT\\Integration', 'stateType': 'Unplanned Downtime', 'stateColor': '#01579B'},
{'stateName': 'Workstation Login Issue', 'stateCategory': 'IT\\Workstation', 'stateType': 'Unplanned Downtime', 'stateColor': '#1565C0'},
{'stateName': 'Ergonomic Adjustment Required', 'stateCategory': 'Safety\\Ergonomics', 'stateType': 'Unplanned Downtime', 'stateColor': '#C62828'},
{'stateName': 'Packing Area Obstruction', 'stateCategory': 'Safety\\Area', 'stateType': 'Unplanned Downtime', 'stateColor': '#D32F2F'},
{'stateName': 'Unexpected Stop', 'stateCategory': 'Unknown', 'stateType': 'Unplanned Downtime', 'stateColor': '#757575'}
]
},
]

allDefinitions = [d for a in ASSETS for d in a['states']]
print '=' * 70
print 'Creating states for %s' % ', '.join(a['assetPath'].split('\\')[-1] for a in ASSETS)
print '=' * 70

# =========================
# Step 0: Check everything we need before changing anything
# =========================
print '\nChecking assets, state types and definitions...'
for a in ASSETS:
a['assetId'] = system.kanoa.utilities.getFieldValue('assetId', system.kanoa.asset.getAssets({'assetPath': a['assetPath']}))
if a['assetId'] is None:
raise Exception('No asset at %s. Check the path.' % a['assetPath'])
names = [d['stateName'] for d in a['states']]
duplicates = sorted(set(n for n in names if names.count(n) > 1))
if duplicates:
raise Exception('States listed more than once for %s: %s' % (a['assetPath'], ', '.join(duplicates)))
print ' %s: assetId %s, %d states' % (a['assetPath'], a['assetId'], len(a['states']))

stateTypes = system.kanoa.utilities.convertDatasetToDict(system.kanoa.asset.getStateType({}), 'stateTypeName', 'stateTypeId')
badTypes = sorted(set(d['stateType'] for d in allDefinitions if d['stateType'] not in stateTypes))
if badTypes:
raise Exception('Unknown state type(s): %s. Valid types: %s' % (', '.join(badTypes), ', '.join(sorted(stateTypes))))

# A state listed for both assets must have the same type; a different colour just uses the first one
firstDefinition = {}
for d in allDefinitions:
key = (d['stateCategory'], d['stateName'])
first = firstDefinition.setdefault(key, d)
if first['stateType'] != d['stateType']:
raise Exception('%s / %s is listed as both %s and %s' % (key + (first['stateType'], d['stateType'])))
if first['stateColor'] != d['stateColor']:
print ' Note: %s / %s is listed with colours %s and %s; using %s' % (key + (first['stateColor'], d['stateColor'], first['stateColor']))

# =========================
# Step 1: Create missing state categories
# =========================
def getParentPath(path):
"""'Mechanical\\Spindle' -> 'Mechanical'; a top-level category has no parent."""
parts = path.split('\\')
return '\\'.join(parts[:-1]) if len(parts) > 1 else None

def ensureStateCategory(categoryPath, stateCategories):
"""Return the category's id, creating it (and any missing parents) if needed."""
if stateCategories.get(categoryPath):
return stateCategories[categoryPath]
parentPath = getParentPath(categoryPath)
parentId = ensureStateCategory(parentPath, stateCategories) if parentPath else None
stateCategoryInfo = {'parentId': parentId, 'stateCategoryName': categoryPath.split('\\')[-1], 'enabled': True}
stateCategoryId, msg = system.kanoa.asset.addStateCategory(stateCategoryInfo, userId)
if msg or stateCategoryId is None:
raise Exception('Could not create state category %s: %s' % (categoryPath, msg))
print ' Created category %s (stateCategoryId %s)' % (categoryPath, stateCategoryId)
stateCategories[categoryPath] = stateCategoryId
return stateCategoryId

print '\n-- Step 1: State categories'
# All categories, including disabled ones, so we never try to create one twice
stateCategories = system.kanoa.utilities.convertDatasetToDict(
system.kanoa.asset.getStateCategory({}), 'stateCategoryPath', 'stateCategoryId')
needed = sorted(set(d['stateCategory'] for d in allDefinitions))
before = len(stateCategories)
for categoryPath in needed:
ensureStateCategory(categoryPath, stateCategories)
print ' %d categories used by these states; %d created (including parent categories)' % (len(needed), len(stateCategories) - before)

# =========================
# Step 2: Create missing states (once, even if both assets use them)
# =========================
print '\n-- Step 2: States'
stateIds = {}
for s in system.kanoa.utilities.convertDatasetToJSON(system.kanoa.asset.getStates({})):
stateIds[(s['stateCategoryPath'], s['stateName'])] = s['stateId']

created, existed = 0, 0
for key, d in sorted(firstDefinition.items()):
if key in stateIds:
existed += 1
continue
stateInfo = {
'stateName': d['stateName'],
'stateColor': d['stateColor'],
'stateCategoryId': stateCategories[d['stateCategory']],
'stateTypeId': stateTypes[d['stateType']],
'assetGroupId': None,
'enabled': True
}
stateId, msg = system.kanoa.asset.addState(stateInfo, userId)
if msg or stateId is None:
print ' FAILED: %s / %s: %s' % (key + (msg,))
continue
print ' Created: %s / %s (stateId %s, %s)' % (key + (stateId, d['stateType']))
stateIds[key] = stateId
created += 1
print ' %d states created, %d already existed' % (created, existed)

# =========================
# Step 3: Link each asset's states with a state code
# =========================
def currentLinks(assetId):
"""{stateId: stateCode} for every state linked to the asset right now."""
links = system.kanoa.utilities.convertDatasetToJSON(system.kanoa.asset.getAssetStates({'assetId': assetId}))
return dict((s['stateId'], s['stateCode']) for s in links)

# Kanoa's docs disagree about linkAssetStates' argument order (the parameter list puts
# stateCodes before userId; the example puts userId first). The example's order is the
# one that has worked, so try it first and fall back to the other, checking each time.
linkOrders = [
('assetIds, stateIds, userId, stateCodes', lambda assetId, ids, codes: system.kanoa.asset.linkAssetStates([assetId], ids, userId, codes)),
('assetIds, stateIds, stateCodes, userId', lambda assetId, ids, codes: system.kanoa.asset.linkAssetStates([assetId], ids, codes, userId)),
]

def linkStates(assetId, ids, codes):
"""Link the states and confirm they were saved. Returns the asset's links afterwards."""
for i, (order, call) in enumerate(linkOrders):
try:
call(assetId, ids, codes)
except:
print ' linkAssetStates(%s) raised: %s' % (order, str(sys.exc_info()[1]).split('\n')[0])
continue
after = currentLinks(assetId)
if all(x in after for x in ids):
if i:
linkOrders.insert(0, linkOrders.pop(i)) # use the order that worked for the next asset
return after
print ' linkAssetStates(%s) returned, but the links were not saved' % order
return currentLinks(assetId)

for a in ASSETS:
assetPath, assetId = a['assetPath'], a['assetId']
print '\n-- Step 3: Links to %s' % assetPath
# All links, including disabled ones, so a new code never reuses one already taken
links = currentLinks(assetId)
usedCodes = set(c for c in links.values() if c is not None)
nextStateCode = max(usedCodes) + 1 if usedCodes else 1
print ' %d states already linked; highest code %s' % (len(links), max(usedCodes) if usedCodes else 'none')

linkIds, linkCodes, linkNames = [], [], []
for d in a['states']:
stateId = stateIds.get((d['stateCategory'], d['stateName']))
if stateId is None:
print ' SKIPPED: %s was not created' % d['stateName']
continue
if stateId in links:
continue
code = d.get('stateCode')
if code is None:
while nextStateCode in usedCodes:
nextStateCode += 1
code = nextStateCode
elif code in usedCodes:
print ' SKIPPED: %s wants code %s, which is already used on this asset' % (d['stateName'], code)
continue
usedCodes.add(code)
linkIds.append(stateId)
linkCodes.append(code)
linkNames.append(d['stateName'])
print ' Will link %-34s code %s' % (d['stateName'], code)

if not linkIds:
print ' Nothing new to link'
continue
after = linkStates(assetId, linkIds, linkCodes)
missing = [n for n, i in zip(linkNames, linkIds) if i not in after]
wrongCode = [(n, c, after[i]) for n, i, c in zip(linkNames, linkIds, linkCodes) if i in after and after[i] != c]
print ' Linked %d of %d state(s) using linkAssetStates(%s)' % (len(linkIds) - len(missing), len(linkIds), linkOrders[0][0])
if missing:
print ' NOT LINKED: %s' % ', '.join(missing)
for name, wanted, got in wrongCode:
print ' WRONG CODE: %s has code %s, expected %s' % (name, got, wanted)

print '\n' + '=' * 70
print 'Done. Check the states on each asset in Configuration > Ops > States.'
print '=' * 70

After running the script, verify in the application that:

  • New state categories were created.
  • New states were created.
  • The states have the correct category, state type, and color, and no asset group (the script sets assetGroupId to None).
  • Machine 1 and Manual Pack 1 each have their new states linked.
  • State codes are present and do not conflict with existing state codes.
Check your work

Copy this script into the Ignition Designer Script Console (Tools → Script Console) and run it after the script. It counts the states linked to Machine 1 and Manual Pack 1: the 4 built-in states plus the 54 and 30 the script links. It doesn't change anything.

Show the check script
checkLesson-scriptStates.py
# Kanoa Ops Training - check "Create States with Scripting" (Scaling across the enterprise, lesson 2)
#
# Run in the Ignition Designer Script Console. It counts the states linked to each
# asset: the 4 built-in states plus the ones the lesson's scripts link.

ASSETS = [
# asset path, states the lesson's script links
('CoreForge\\Philadelphia\\Machining\\Machine 1', 54),
('CoreForge\\Philadelphia\\Finishing\\Manual Pack 1', 30),
]
BUILT_IN = 4

results = []
print '=' * 70
print 'Kanoa Ops Training - Create States with Scripting check'
print '=' * 70
for assetPath, scripted in ASSETS:
name = assetPath.split('\\')[-1]
assetId = system.kanoa.utilities.getFieldValue('assetId', system.kanoa.asset.getAssets({'assetPath': assetPath}))
if assetId is None:
results.append(False)
print 'FAIL %s not found at %s' % (name, assetPath)
continue
count = len(system.kanoa.utilities.convertDatasetToJSON(system.kanoa.asset.getAssetStates({'assetId': assetId})))
expected = BUILT_IN + scripted
ok = count >= expected
results.append(ok)
if ok:
print 'PASS %s has %d state links (expected at least %d)' % (name, count, expected)
else:
print 'FAIL %s has %d state links, expected at least %d' % (name, count, expected)
print ' -> Run the %s script again. It only creates and links what is missing.' % name

print '=' * 70
print 'Result: %d of %d checks passed' % (results.count(True), len(results))
print '=' * 70