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.

::::action[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.

<details>
<summary>View the generateStates script</summary>

```python title="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
```

</details>

::::

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

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.

<details>
<summary>Show the check script</summary>

::check-script{src="ops/07-scaling-across-the-enterprise/2-script-states.py" title="checkLesson-scriptStates.py"}

</details>

:::
