Configure a Measure
Machine 1's coolant temperature drifts as the spindle works harder, and engineering wants to see that trend without storing every reading. You'll simulate the signal, then summarize it with a measure.
-
In Ignition Designer, in the
Kanoatag provider, open[Kanoa]CoreForge/Philadelphia/Machining/Machine 1. -
Create an Expression tag named
Coolant Temp, data type Float. -
Set its expression to:
22 + 1.5 * sin(toMillis(now(1000)) / 300000.0) + 0.3 * sin(toMillis(now(1000)) / 7000.0)This gives a slow swing of roughly 30 minutes of ±1.5 °C around 22 °C, with a faster ripple on top, updating once a second.
-
Confirm the value changes in the tag browser.
- Navigate to Configuration → Ops → Measures.
- Create a measure named
Coolant Temperatureand link it to Machine 1. - Set its aggregation to an average, adding one with the aggregation editor if none exists yet.
- Enable the Tag checkbox and save.
Enabling Tag creates a measure UDT instance in a measures folder under Machine 1's tags in the Kanoa provider. The UDT does the collecting. It holds values in memory and, when the window is full, calculates the statistics and writes one record to the database.
- Open the tag browser in the designer, and find the new
Coolant Temperatureinstance under Machine 1'smeasuresfolder. - Edit the
measureValuetag. Change theValue Sourceto aReference Tagand point theSource Tag Pathto[.]../../Coolant Temp. - Set
maxSampleCountto60andmaxSampleDurationSecsto60, so a record is written about once a minute. - Set
enabledtotrue. - Watch the
datadataset fill with samples, andupdatefire when the window completes.
| Window setting | Use it when |
|---|---|
maxSampleCount | Values arrive at a steady rate and you want a fixed number per record |
maxSampleDurationSecs | You want one record per time period, regardless of how often the value changes |
Because each record keeps its sampleCount, averages across many windows, lines or sites can be weighted correctly later, rather than treating every window as one data point.
Copy this script into the Ignition Designer Script Console (Tools → Script Console) and run it. It checks the Coolant Temp simulator tag, the Coolant Temperature measure and its average aggregation, the measure tag and its settings, and that records are being written. Each check prints PASS or FAIL with what to fix. It doesn't change anything.
Show the check script
# Kanoa Ops Training - check "Configure a Measure" (Measures, lesson 1)
#
# Run in the Ignition Designer Script Console (Tools > Script Console), in the
# project that inherits KANOA_SCRIPTS. It only reads; it changes nothing.
# Every check prints PASS or FAIL with what to fix, then a summary.
ASSET = 'CoreForge\\Philadelphia\\Machining\\Machine 1'
MEASURE = 'Coolant Temperature'
SIM_TAG = '[Kanoa]CoreForge/Philadelphia/Machining/Machine 1/Coolant Temp'
SAMPLE_COUNT = 60
SAMPLE_SECS = 60
LOOKBACK_MINS = 60
import time
results = []
def check(ok, what, fix=None):
results.append(bool(ok))
if ok:
print 'PASS %s' % what
else:
print 'FAIL %s' % what
if fix:
print ' -> %s' % fix
def note(what):
print 'NOTE %s' % what
def field(row, *names):
for n in names:
if n in row and row[n] is not None:
return row[n]
return None
def text(v):
return '' if v is None else unicode(v).strip()
def key(v):
return ' '.join(text(v).lower().split())
def truthy(v):
return str(v).lower() in ('true', '1')
def rows(data):
# Kanoa returns a dataset, a list of dictionaries, or None
if data is None:
return []
if hasattr(data, 'getRowCount'):
cols = list(data.getColumnNames())
return [dict((c, data.getValueAt(i, c)) for c in cols) for i in range(data.getRowCount())]
return [dict(r) for r in data]
def exists(path):
try:
return bool(path) and system.tag.exists(path)
except:
return False
def read(path):
return system.tag.readBlocking([path])[0]
def isGood(qv):
try:
return qv.quality.isGood()
except:
return False
def normTag(path):
return text(path).replace('\\', '/').rstrip('/').lower()
def resolve(base, ref):
"""Resolve a [.] relative reference against the folder holding the tag."""
ref = text(ref)
if not ref.startswith('[.]'):
return ref
parts = base.split('/')
for p in ref[3:].split('/'):
if p == '..':
parts.pop()
elif p and p != '.':
parts.append(p)
return '/'.join(parts)
def setting(udt, name):
"""A measure UDT setting: a member tag, or a UDT parameter."""
if exists(udt + '/' + name):
qv = read(udt + '/' + name)
else:
qv = read(udt + '/Parameters.' + name)
return qv.value if isGood(qv) else None
print '=' * 70
print 'Kanoa Ops Training - Configure a Measure check'
print '=' * 70
# 1. The simulated signal
print '\n-- Simulated coolant temperature'
simOk = exists(SIM_TAG)
check(simOk, 'Coolant Temp tag exists',
'In the Kanoa provider, under CoreForge/Philadelphia/Machining/Machine 1, create an Expression tag named Coolant Temp.')
if simOk:
first = read(SIM_TAG)
check(isGood(first), 'Coolant Temp reads a good value',
"Its quality is %s. Check the expression; it should be the one in this lesson." % first.quality)
if isGood(first):
try:
v = float(first.value)
check(19.0 <= v <= 25.0, 'Coolant Temp is around 22 degrees (%.2f)' % v,
'Expected 20.5 to 23.5. Check the expression matches the lesson.')
except:
check(False, 'Coolant Temp is a number', "It's '%s'. Set its data type to Float." % text(first.value))
time.sleep(1.5)
second = read(SIM_TAG)
if text(second.value) == text(first.value):
note('Coolant Temp did not change in 1.5 seconds. It should update every second; check the now(1000) in the expression.')
# 2. The measure definition
print '\n-- Measure definition'
measures = rows(system.kanoa.measures.getMeasures({'assetPath': ASSET}))
exact = [m for m in measures if text(field(m, 'measureName')) == MEASURE]
close = [m for m in measures if key(field(m, 'measureName')) == key(MEASURE)]
measure = (exact or close or [None])[0]
measureId = None
if measure is None:
others = ', '.join(text(field(m, 'measureName')) for m in measures)
check(False, '%s measure exists on Machine 1' % MEASURE,
'In Configuration > Ops > Measures, create a measure named %s linked to Machine 1.%s'
% (MEASURE, (' Measures on Machine 1: %s.' % others) if others else ''))
else:
measureId = field(measure, 'measureId')
actual = text(field(measure, 'measureName'))
check(actual == MEASURE, '%s measure exists on Machine 1' % MEASURE, "Found '%s'. Rename it to %s." % (actual, MEASURE))
if field(measure, 'enabled') is not None:
check(truthy(field(measure, 'enabled')), '%s is enabled' % MEASURE, 'Edit the measure and tick Enabled.')
aggName = text(field(measure, 'aggregationName', 'displayName', 'measureAggregationName'))
if not aggName and field(measure, 'measureAggregationId') is not None:
aggs = rows(system.kanoa.measures.getMeasureAggregations({'measureAggregationId': field(measure, 'measureAggregationId')}))
if aggs:
aggName = text(field(aggs[0], 'displayName', 'aggregationName'))
isAvg = any(w in key(aggName) for w in ('avg', 'average', 'mean'))
check(isAvg, '%s uses an average aggregation' % MEASURE,
"Its aggregation is '%s'. Edit the measure and set an average aggregation (add one with the aggregation editor if needed)."
% (aggName or 'not set'))
# 3. The measure tag
print '\n-- Measure tag'
udt = None
if measure is not None:
try:
base = text(system.kanoa.asset.getAssetTagPath({'assetPath': ASSET}))
except:
base = ''
path = base + '/measures/' + text(field(measure, 'measureName'))
if base and exists(path):
udt = path
check(udt is not None, '%s tag exists under Machine 1\'s measures folder' % MEASURE,
"No tag at '%s'. Edit the measure, tick Tag and save." % (path if base else 'measures/' + MEASURE))
if udt:
mid = setting(udt, 'measureId')
if mid is not None and measureId is not None:
check(text(mid) == text(measureId), 'The tag\'s measureId matches the measure (%s)' % measureId,
"The tag has measureId %s. Delete the tag, then untick and re-tick Tag on the measure and save." % text(mid))
valueTag = None
for name in ('measureValue', 'value'):
if exists(udt + '/' + name):
valueTag = udt + '/' + name
break
check(valueTag is not None, 'The measure tag has a measureValue tag', "Couldn't find measureValue under %s." % udt)
if valueTag:
try:
cfg = system.tag.getConfiguration(valueTag, False)[0]
source = text(cfg.get('valueSource'))
ref = text(cfg.get('sourceTagPath'))
except:
source, ref = '', ''
check(source.lower() == 'reference', 'measureValue is a Reference tag',
"Its value source is '%s'. Edit measureValue and set Value Source to Reference Tag." % (source or 'unknown'))
if source.lower() == 'reference':
target = resolve(valueTag.rsplit('/', 1)[0], ref)
check(normTag(target) == normTag(SIM_TAG), 'measureValue points at the Coolant Temp tag',
"Its Source Tag Path '%s' resolves to '%s'. Set it to %s" % (ref, target, SIM_TAG))
qv = read(valueTag)
check(isGood(qv), 'measureValue reads a good value (%s)' % text(qv.value),
'Its quality is %s. Check its Source Tag Path points at %s.' % (qv.quality, SIM_TAG))
for name, expected in (('maxSampleCount', SAMPLE_COUNT), ('maxSampleDurationSecs', SAMPLE_SECS)):
v = setting(udt, name)
check(text(v) == str(expected), '%s is %d' % (name, expected),
"It's %s. Set %s on the %s tag to %d." % (text(v) or 'not set', name, MEASURE, expected))
v = setting(udt, 'enabled')
check(truthy(v), 'The measure tag is enabled', "enabled is %s. Set it to true." % (text(v) or 'not set'))
# 4. Records written to the database
if measureId is not None:
print '\n-- Measure records'
end = system.date.now()
start = system.date.addMinutes(end, -LOOKBACK_MINS)
try:
events = rows(system.kanoa.measures.getMeasureEvents({'measureId': measureId, 'tStampStart': start, 'tStampEnd': end}))
except:
events = []
check(events, '%s has written records in the last %d minutes' % (MEASURE, LOOKBACK_MINS),
'Nothing yet. With enabled true and a 60 second window, a record is written about once a minute. '
'Fix any FAIL above, wait a minute or two, then run this again.')
if events:
last = events[-1]
note('%d record(s). Latest: mean %s, samples %s' % (len(events),
text(field(last, 'mean', 'measureValue', 'value')) or '?', text(field(last, 'sampleCount')) or '?'))
passed = results.count(True)
print '\n' + '=' * 70
print 'Result: %d of %d checks passed' % (passed, len(results))
if passed == len(results):
print 'All good. Your measure is collecting.'
else:
print 'Fix the FAIL items above, then run this again.'
print '=' * 70