Add base config schema and ConfigManager.buildConfig()
- New baseConfig.json: shared schema for general/logging/functionality/asset sections - ConfigManager.buildConfig(): builds runtime config from UI inputs + domain overrides - Eliminates the need for each nodeClass to manually construct base config sections - All nodes can now use: cfgMgr.buildConfig(name, uiConfig, nodeId, domainConfig) Closes #1 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
85
src/configs/baseConfig.json
Normal file
85
src/configs/baseConfig.json
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
{
|
||||||
|
"general": {
|
||||||
|
"name": {
|
||||||
|
"default": "Unnamed Node",
|
||||||
|
"rules": { "type": "string", "description": "Human-readable name for this node." }
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"default": null,
|
||||||
|
"rules": { "type": "string", "nullable": true, "description": "Unique node identifier (set at runtime)." }
|
||||||
|
},
|
||||||
|
"unit": {
|
||||||
|
"default": "unitless",
|
||||||
|
"rules": { "type": "string", "description": "Default measurement unit." }
|
||||||
|
},
|
||||||
|
"logging": {
|
||||||
|
"logLevel": {
|
||||||
|
"default": "info",
|
||||||
|
"rules": {
|
||||||
|
"type": "enum",
|
||||||
|
"values": [
|
||||||
|
{ "value": "debug", "description": "Verbose diagnostic messages." },
|
||||||
|
{ "value": "info", "description": "General informational messages." },
|
||||||
|
{ "value": "warn", "description": "Warning messages." },
|
||||||
|
{ "value": "error", "description": "Error level messages only." }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"default": true,
|
||||||
|
"rules": { "type": "boolean", "description": "Enable or disable logging." }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"functionality": {
|
||||||
|
"softwareType": {
|
||||||
|
"default": "unknown",
|
||||||
|
"rules": { "type": "string", "description": "Software type identifier for parent-child registration." }
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"default": "Generic EVOLV node",
|
||||||
|
"rules": { "type": "string", "description": "Describes the functional role of this node." }
|
||||||
|
},
|
||||||
|
"positionVsParent": {
|
||||||
|
"default": "atEquipment",
|
||||||
|
"rules": {
|
||||||
|
"type": "enum",
|
||||||
|
"values": [
|
||||||
|
{ "value": "upstream", "description": "Upstream of parent equipment." },
|
||||||
|
{ "value": "atEquipment", "description": "At equipment level." },
|
||||||
|
{ "value": "downstream", "description": "Downstream of parent equipment." }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"asset": {
|
||||||
|
"uuid": {
|
||||||
|
"default": null,
|
||||||
|
"rules": { "type": "string", "nullable": true, "description": "Asset UUID from asset management system." }
|
||||||
|
},
|
||||||
|
"tagCode": {
|
||||||
|
"default": null,
|
||||||
|
"rules": { "type": "string", "nullable": true, "description": "Asset tag code." }
|
||||||
|
},
|
||||||
|
"supplier": {
|
||||||
|
"default": "Unknown",
|
||||||
|
"rules": { "type": "string", "description": "Equipment supplier." }
|
||||||
|
},
|
||||||
|
"category": {
|
||||||
|
"default": "sensor",
|
||||||
|
"rules": { "type": "string", "description": "Asset category." }
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"default": "Unknown",
|
||||||
|
"rules": { "type": "string", "description": "Asset type." }
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"default": "Unknown",
|
||||||
|
"rules": { "type": "string", "description": "Equipment model." }
|
||||||
|
},
|
||||||
|
"unit": {
|
||||||
|
"default": "unitless",
|
||||||
|
"rules": { "type": "string", "description": "Asset measurement unit." }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -47,6 +47,69 @@ class ConfigManager {
|
|||||||
return fs.existsSync(configPath);
|
return fs.existsSync(configPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a runtime config by merging base schema + node schema + UI overrides.
|
||||||
|
* Eliminates the need for each nodeClass to manually construct general/asset/functionality sections.
|
||||||
|
*
|
||||||
|
* @param {string} nodeName - Node type name (e.g., 'valve', 'measurement')
|
||||||
|
* @param {object} uiConfig - Raw config from Node-RED UI
|
||||||
|
* @param {string} nodeId - Node-RED node ID (from node.id)
|
||||||
|
* @param {object} [domainConfig={}] - Domain-specific config sections (e.g., { scaling: {...}, smoothing: {...} })
|
||||||
|
* @returns {object} Merged runtime config
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const cfgMgr = new ConfigManager();
|
||||||
|
* const config = cfgMgr.buildConfig('measurement', uiConfig, node.id, {
|
||||||
|
* scaling: { enabled: uiConfig.scaling, inputMin: uiConfig.i_min, ... },
|
||||||
|
* smoothing: { smoothWindow: uiConfig.count, ... }
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
buildConfig(nodeName, uiConfig, nodeId, domainConfig = {}) {
|
||||||
|
// Build base sections from UI config (common to ALL nodes)
|
||||||
|
const config = {
|
||||||
|
general: {
|
||||||
|
name: uiConfig.name || nodeName,
|
||||||
|
id: nodeId,
|
||||||
|
unit: uiConfig.unit || 'unitless',
|
||||||
|
logging: {
|
||||||
|
enabled: uiConfig.enableLog !== undefined ? uiConfig.enableLog : true,
|
||||||
|
logLevel: uiConfig.logLevel || 'info'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
functionality: {
|
||||||
|
softwareType: nodeName,
|
||||||
|
positionVsParent: uiConfig.positionVsParent || 'atEquipment',
|
||||||
|
distance: uiConfig.hasDistance ? uiConfig.distance : undefined
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add asset section if UI provides asset fields
|
||||||
|
if (uiConfig.supplier || uiConfig.category || uiConfig.assetType || uiConfig.model) {
|
||||||
|
config.asset = {
|
||||||
|
uuid: uiConfig.uuid || uiConfig.assetUuid || null,
|
||||||
|
tagCode: uiConfig.tagCode || uiConfig.assetTagCode || null,
|
||||||
|
supplier: uiConfig.supplier || 'Unknown',
|
||||||
|
category: uiConfig.category || 'sensor',
|
||||||
|
type: uiConfig.assetType || 'Unknown',
|
||||||
|
model: uiConfig.model || 'Unknown',
|
||||||
|
unit: uiConfig.unit || 'unitless'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge domain-specific sections
|
||||||
|
Object.assign(config, domainConfig);
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the base config schema (shared across all nodes).
|
||||||
|
* @returns {object} Base config schema
|
||||||
|
*/
|
||||||
|
getBaseConfig() {
|
||||||
|
return this.getConfig('baseConfig');
|
||||||
|
}
|
||||||
|
|
||||||
createEndpoint(nodeName) {
|
createEndpoint(nodeName) {
|
||||||
try {
|
try {
|
||||||
// Load the config for this node
|
// Load the config for this node
|
||||||
|
|||||||
Reference in New Issue
Block a user