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>
126 lines
4.4 KiB
JavaScript
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;
|