Files
generalFunctions/src/helper/childRegistrationUtils.js
znetsixe 29b78a3f9b fix(childRegistrationUtils): alias rotatingmachine/machinegroupcontrol so production parents see them
The MGC and pumpingStation registerChild handlers dispatch on
softwareType === 'machine' / 'machinegroup' / 'pumpingstation' /
'measurement'. But buildConfig sets functionality.softwareType to the
lowercased node name, so in production rotatingMachine reports
'rotatingmachine' and machineGroupControl reports 'machinegroupcontrol'.
Result: the MGC <-> rotatingMachine and pumpingStation <-> MGC wiring
silently never hit the right branch in production, even though every
unit test passes (tests pass an already-aliased softwareType manually).

Fix: tiny SOFTWARE_TYPE_ALIASES map at the central registerChild
dispatcher in childRegistrationUtils. Real production names get
translated to the dispatch keys parents already check for, while tests
that pass already-aliased keys are unaffected (their values aren't in
the alias map and pass through unchanged).

  rotatingmachine        -> machine
  machinegroupcontrol    -> machinegroup

Verified end-to-end on Dockerized Node-RED: MGC now reports
'3 machine(s) connected' when wired to 3 rotatingMachine ports;
pumpingStation registers MGC as a machinegroup child and listens to
its predicted-flow stream.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 15:53:21 +02:00

126 lines
4.4 KiB
JavaScript

// Map a child's raw softwareType (the lowercased node name from
// buildConfig) to the "role" key that parent registerChild() handlers
// dispatch on. Without this, MGC/pumpingStation register-handlers (which
// branch on 'machine' / 'machinegroup' / 'pumpingstation' / 'measurement')
// silently miss every real production child because rotatingMachine
// reports softwareType='rotatingmachine' and machineGroupControl reports
// 'machinegroupcontrol'. Existing tests that pass already-aliased keys
// ('machine', 'machinegroup') stay green because those aren't in the
// alias map and pass through unchanged.
const SOFTWARE_TYPE_ALIASES = {
rotatingmachine: 'machine',
machinegroupcontrol: 'machinegroup',
};
class ChildRegistrationUtils {
constructor(mainClass) {
this.mainClass = mainClass;
this.logger = mainClass.logger;
this.registeredChildren = new Map();
}
async registerChild(child, positionVsParent, distance) {
if (!child || typeof child !== 'object') {
this.logger?.warn('registerChild skipped: invalid child payload');
return false;
}
if (!child.config?.functionality || !child.config?.general) {
this.logger?.warn('registerChild skipped: missing child config/functionality/general');
return false;
}
const rawSoftwareType = (child.config.functionality.softwareType || '').toLowerCase();
const softwareType = SOFTWARE_TYPE_ALIASES[rawSoftwareType] || rawSoftwareType;
const name = child.config.general.name || child.config.general.id || 'unknown';
const id = child.config.general.id || name;
this.logger.debug(`Registering child: ${name} (${id}) as ${softwareType} at ${positionVsParent}`);
// Enhanced child setup - multiple parents
if (Array.isArray(child.parent)) {
child.parent.push(this.mainClass);
} else {
child.parent = [this.mainClass];
}
child.positionVsParent = positionVsParent;
// Enhanced measurement container with rich context
if (child.measurements) {
child.measurements.setChildId(id);
child.measurements.setChildName(name);
child.measurements.setParentRef(this.mainClass);
}
// Store child in your expected structure
this._storeChild(child, softwareType);
// Track registration for utilities
this.registeredChildren.set(id, {
child,
softwareType,
position: positionVsParent,
registeredAt: Date.now()
});
// IMPORTANT: Only call parent registration - no automatic handling and if parent has this function then try to register this child
if (typeof this.mainClass.registerChild === 'function') {
return this.mainClass.registerChild(child, softwareType);
}
this.logger.info(`✅ Child ${name} registered successfully`);
return true;
}
_storeChild(child, softwareType) {
// Maintain your existing structure
if (!this.mainClass.child) this.mainClass.child = {};
const typeKey = softwareType || 'unknown';
if (!this.mainClass.child[typeKey]) this.mainClass.child[typeKey] = {};
const { category = "sensor" } = child.config.asset || {};
if (!this.mainClass.child[typeKey][category]) {
this.mainClass.child[typeKey][category] = [];
}
this.mainClass.child[typeKey][category].push(child);
}
// NEW: Utility methods for parent to use
getChildrenOfType(softwareType, category = null) {
if (!this.mainClass.child[softwareType]) return [];
if (category) {
return this.mainClass.child[softwareType][category] || [];
}
// Return all children of this software type
return Object.values(this.mainClass.child[softwareType]).flat();
}
getChildById(childId) {
return this.registeredChildren.get(childId)?.child || null;
}
getAllChildren() {
return Array.from(this.registeredChildren.values()).map(r => r.child);
}
// NEW: Debugging utilities
logChildStructure() {
this.logger.debug('Current child structure:', JSON.stringify(
Object.keys(this.mainClass.child).reduce((acc, softwareType) => {
acc[softwareType] = Object.keys(this.mainClass.child[softwareType]).reduce((catAcc, category) => {
catAcc[category] = this.mainClass.child[softwareType][category].map(c => ({
id: c.config.general.id,
name: c.config.general.name
}));
return catAcc;
}, {});
return acc;
}, {}), null, 2
));
}
}
module.exports = ChildRegistrationUtils;