Files
generalFunctions/src/helper/outputUtils.js

121 lines
3.7 KiB
JavaScript
Raw Normal View History

const { getFormatter } = require('./formatters');
2025-06-10 12:36:39 +02:00
//this class will handle the output events for the node red node
class OutputUtils {
constructor() {
this.output = {};
2025-06-10 12:36:39 +02:00
}
checkForChanges(output, format) {
2026-02-23 13:17:47 +01:00
if (!output || typeof output !== 'object') {
return {};
}
this.output[format] = this.output[format] || {};
2025-06-10 12:36:39 +02:00
const changedFields = {};
for (const key in output) {
if (Object.prototype.hasOwnProperty.call(output, key) && output[key] !== this.output[format][key]) {
2025-06-10 12:36:39 +02:00
let value = output[key];
// For fields: if the value is an object (and not a Date), stringify it.
if (value !== null && typeof value === 'object' && !(value instanceof Date)) {
changedFields[key] = JSON.stringify(value);
} else {
changedFields[key] = value;
}
}
}
// Update the saved output state.
this.output[format] = { ...this.output[format], ...changedFields };
return changedFields;
}
formatMsg(output, config, format) {
let msg = {};
// Compare output with last output and only include changed values
const changedFields = this.checkForChanges(output,format);
if (Object.keys(changedFields).length > 0) {
// Fall back to `<softwareType>_<id>` when `general.name` is unset —
// the original convention before name became a registered config field.
const measurement = config.general.name
|| `${config.functionality?.softwareType}_${config.general.id}`;
const flatTags = this.flattenTags(this.extractRelevantConfig(config));
const formatterName = this.resolveFormatterName(config, format);
const formatter = getFormatter(formatterName);
const payload = formatter.format(measurement, {
fields: changedFields,
tags: flatTags,
config,
channel: format,
});
msg = this.wrapMessage(measurement, payload);
2025-06-10 12:36:39 +02:00
return msg;
}
2026-02-23 13:17:47 +01:00
return null;
2025-06-10 12:36:39 +02:00
}
resolveFormatterName(config, channel) {
const outputConfig = config.output || {};
if (channel === 'process') {
return outputConfig.process || 'process';
}
if (channel === 'influxdb') {
return outputConfig.dbase || 'influxdb';
}
return outputConfig[channel] || channel;
}
2025-06-10 12:36:39 +02:00
wrapMessage(measurement, payload) {
return {
topic: measurement,
payload,
};
2025-06-10 12:36:39 +02:00
}
flattenTags(obj) {
const result = {};
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
2025-06-10 12:36:39 +02:00
const value = obj[key];
if (value !== null && typeof value === 'object' && !(value instanceof Date)) {
// Recursively flatten the nested object.
const flatChild = this.flattenTags(value);
for (const childKey in flatChild) {
if (Object.prototype.hasOwnProperty.call(flatChild, childKey)) {
2025-06-10 12:36:39 +02:00
result[`${key}_${childKey}`] = String(flatChild[childKey]);
}
}
} else {
// InfluxDB tags must be strings.
result[key] = String(value);
}
}
}
return result;
}
extractRelevantConfig(config) {
2025-06-10 12:36:39 +02:00
return {
// general properties
id: config.general?.id,
// functionality properties
softwareType: config.functionality?.softwareType,
role: config.functionality?.role,
positionVsParent: config.functionality?.positionVsParent,
2025-06-10 12:36:39 +02:00
// asset properties (exclude machineCurve)
uuid: config.asset?.uuid,
tagcode: config.asset?.tagCode || config.asset?.tagcode,
2025-06-10 12:36:39 +02:00
geoLocation: config.asset?.geoLocation,
category: config.asset?.category,
2025-06-10 12:36:39 +02:00
type: config.asset?.type,
model: config.asset?.model,
unit: config.general?.unit,
2025-06-10 12:36:39 +02:00
};
}
}
module.exports = OutputUtils;