can take arbitrary expressions

This commit is contained in:
Scott Bell 2024-08-13 16:21:08 -05:00
parent 0326e38f5d
commit 413338d3e2
4 changed files with 94 additions and 54 deletions

View File

@ -29,10 +29,27 @@ export default class CompsManager extends EventEmitter {
this.persist();
}
getDomainObjectForParameter(keyString) {
getParameters() {
const parameters = this.#domainObject.configuration.comps.parameters;
const parametersWithTimeKey = parameters.map((parameter) => {
return {
...parameter,
timeKey: this.#telemetryCollections[parameter.keyString]?.timeKey
};
});
return parametersWithTimeKey;
}
getTelemetryObjectForParameter(keyString) {
return this.#telemetryObjects[keyString];
}
getMetaDataValuesForParameter(keyString) {
const telemetryObject = this.getTelemetryObjectForParameter(keyString);
const metaData = this.#openmct.telemetry.getMetadata(telemetryObject);
return metaData.valueMetadatas;
}
deleteParameter(keyString) {
this.#domainObject.configuration.comps.parameters =
this.#domainObject.configuration.comps.parameters.filter(
@ -54,6 +71,10 @@ export default class CompsManager extends EventEmitter {
'configuration.comps',
this.#domainObject.configuration.comps
);
console.debug(
`📦 CompsManager: persisted domain object`,
this.#domainObject.configuration.comps
);
}
async load() {
@ -170,7 +191,7 @@ export default class CompsManager extends EventEmitter {
}
getExpression() {
return this.#domainObject.configuration.expression;
return this.#domainObject.configuration.comps.expression;
}
#waitForDebounce() {

View File

@ -8,12 +8,12 @@ onconnect = function (e) {
port.onmessage = function (event) {
console.debug('🧮 Comps Math Worker message:', event);
try {
const { type, callbackID, telemetryForComps, expression } = event.data;
const { type, callbackID, telemetryForComps, expression, parameters } = event.data;
if (type === 'calculateRequest') {
const result = calculateRequest(telemetryForComps, expression);
const result = calculateRequest(telemetryForComps, parameters, expression);
port.postMessage({ type: 'calculationRequestResult', callbackID, result });
} else if (type === 'calculateSubscription') {
const result = calculateSubscription(telemetryForComps, expression);
const result = calculateSubscription(telemetryForComps, parameters, expression);
if (result.length) {
port.postMessage({ type: 'calculationSubscriptionResult', callbackID, result });
}
@ -38,31 +38,48 @@ function getFullDataFrame(telemetryForComps) {
return dataFrame;
}
function calculateSubscription(telemetryForComps, expression) {
function calculateSubscription(telemetryForComps, parameters, expression) {
const dataFrame = getFullDataFrame(telemetryForComps);
return calculate(dataFrame, expression);
return calculate(dataFrame, parameters, expression);
}
function calculateRequest(telemetryForComps, expression) {
function calculateRequest(telemetryForComps, parameters, expression) {
const dataFrame = getFullDataFrame(telemetryForComps);
return calculate(dataFrame, expression);
return calculate(dataFrame, parameters, expression);
}
function calculate(dataFrame, expression) {
function calculate(dataFrame, parameters, expression) {
const sumResults = [];
// Iterate over the first dataset and check for matching utc in the other dataset
const firstDataSet = Object.values(dataFrame)[0];
const secondDataSet = Object.values(dataFrame)[1];
if (!firstDataSet || !secondDataSet) {
// ensure all parameter keyStrings have corresponding telemetry data
if (!expression) {
return sumResults;
}
for (const [utc, item1] of firstDataSet.entries()) {
if (secondDataSet.has(utc)) {
const item2 = secondDataSet.get(utc);
const output = evaluate(expression, { a: item1.sin, b: item2.sin });
sumResults.push({ utc, output });
// take the first parameter keyString as the reference
const referenceParameter = parameters[0];
const otherParameters = parameters.slice(1);
// iterate over the reference telemetry data
const referenceTelemetry = dataFrame[referenceParameter.keyString];
referenceTelemetry.forEach((referenceTelemetryItem) => {
const scope = {
[referenceParameter.name]: referenceTelemetryItem[referenceParameter.valueToUse]
};
const referenceTime = referenceTelemetryItem[referenceParameter.timeKey];
// iterate over the other parameters to set the scope
let missingData = false;
otherParameters.forEach((parameter) => {
const otherDataFrame = dataFrame[parameter.keyString];
const otherTelemetry = otherDataFrame.get(referenceTime);
if (!otherTelemetry) {
missingData = true;
return;
}
scope[parameter.name] = otherTelemetry[parameter.valueToUse];
});
if (missingData) {
return;
}
}
const output = evaluate(expression, scope);
sumResults.push({ [referenceParameter.timeKey]: referenceTime, output });
});
return sumResults;
}

View File

@ -59,21 +59,16 @@ export default class CompsTelemetryProvider {
this.#compsManagerPool
);
specificCompsManager.load().then(() => {
console.debug('📚 specific comp is ready');
const callbackID = this.#getCallbackID();
const telemetryForComps = specificCompsManager.requestUnderlyingTelemetry();
const expression = specificCompsManager.getExpression();
console.debug('🏟️ 1 Telemetry for comps:', telemetryForComps);
console.debug(
'🏟️ 2 Telemetry for comps:',
specificCompsManager.requestUnderlyingTelemetry()
);
console.debug('🏟️ expression:', expression);
const parameters = specificCompsManager.getParameters();
this.#requestPromises[callbackID] = { resolve, reject };
this.#sharedWorker.port.postMessage({
type: 'calculateRequest',
telemetryForComps,
expression,
parameters,
callbackID
});
});
@ -83,11 +78,12 @@ export default class CompsTelemetryProvider {
#computeOnNewTelemetry(specificCompsManager, newTelemetry, callbackID) {
const expression = specificCompsManager.getExpression();
const telemetryForComps = specificCompsManager.getFullDataFrame(newTelemetry);
// console.debug('🏟️ created new Data frame:', telemetryForComps);
const parameters = specificCompsManager.getParameters();
this.#sharedWorker.port.postMessage({
type: 'calculateSubscription',
telemetryForComps,
expression,
parameters,
callbackID
});
}
@ -120,7 +116,6 @@ export default class CompsTelemetryProvider {
this.#sharedWorker.port.onmessageerror = this.onSharedWorkerMessageError.bind(this);
this.#sharedWorker.port.start();
// send an initial message to the worker
this.#sharedWorker.port.postMessage({ type: 'init' });
this.#openmct.on('destroy', () => {
@ -131,10 +126,10 @@ export default class CompsTelemetryProvider {
onSharedWorkerMessage(event) {
const { type, result, callbackID } = event.data;
if (type === 'calculationSubscriptionResult' && this.#subscriptionCallbacks[callbackID]) {
console.log('📝 Shared worker subscription message:', event.data);
console.debug('📝 Shared worker subscription message:', event.data);
this.#subscriptionCallbacks[callbackID](result);
} else if (type === 'calculationRequestResult' && this.#requestPromises[callbackID]) {
console.log('📝 Shared worker request message:', event.data);
console.debug('📝 Shared worker request message:', event.data);
this.#requestPromises[callbackID].resolve(result);
delete this.#requestPromises[callbackID];
} else if (type === 'error') {

View File

@ -55,30 +55,26 @@
:class="{ 's-status-icon-warning-lo': !domainObject.configuration.comps.parameters }"
>
<div v-for="parameter in parameters" :key="parameter.name">
<div class="c-cs__telemetry-reference">
<input
v-model="parameter.name"
class="c-cs__telemetry-reference__label"
@change="compsManager.persist"
/>
<div>
Reference
<input v-model="parameter.name" @change="compsManager.persist" />
<ObjectPath
:domain-object="compsManager.getDomainObjectForParameter(parameter.keyString)"
:domain-object="compsManager.getTelemetryObjectForParameter(parameter.keyString)"
/>
{{ compsManager.getDomainObjectForParameter(parameter.keyString).name }}
{{ compsManager.getTelemetryObjectForParameter(parameter.keyString).name }}
<!-- drop down to select value from telemetry -->
<select
v-model="parameter.value"
class="c-cs__telemetry-reference__value"
@change="compsManager.persist"
>
<select v-model="parameter.valueToUse" @change="persistParameters">
<option
v-for="parameterValueOption in compsManager.getDomainObjectForParameter(
v-for="parameterValueOption in compsManager.getMetaDataValuesForParameter(
parameter.keyString
)"
:key="parameterValueOption"
:value="parameterValueOption"
/>
:key="parameterValueOption.key"
:value="parameterValueOption.key"
>
{{ parameterValueOption.name }}
</option>
</select>
<input v-model="parameter.testValue" @change="compsManager.persist" />
</div>
</div>
<template v-if="!domainObject.configuration.comps.parameters"
@ -92,13 +88,12 @@
<div class="c-cs__header-label c-section__label">Expression</div>
</div>
<div class="c-cs__content">
<div v-if="!isEditing">{{ domainObject.configuration.comps.expression }}</div>
<div v-else>
<div>
<textarea
v-model="domainObject.configuration.comps.expression"
v-model="expression"
class="c-cs__expression__input"
placeholder="Enter an expression"
@change="compsManager.persist"
@change="persistExpression"
></textarea>
</div>
</div>
@ -119,6 +114,7 @@ const compsManager = CompsManager.getCompsManager(domainObject, openmct, compsMa
const currentCompOutput = ref(null);
const testDataApplied = ref(false);
const parameters = ref(null);
const expression = ref(null);
let outputTelemetryCollection;
@ -135,9 +131,20 @@ onBeforeMount(async () => {
outputTelemetryCollection.on('clear', clearData);
await outputTelemetryCollection.load();
await compsManager.load();
parameters.value = domainObject.configuration.comps.parameters;
parameters.value = compsManager.getParameters();
expression.value = compsManager.getExpression();
});
function persistParameters() {
domainObject.configuration.comps.parameters = parameters.value;
compsManager.persist();
}
function persistExpression() {
domainObject.configuration.comps.expression = expression.value;
compsManager.persist();
}
function applyTestData() {}
function telemetryProcessor(data) {