Compare commits
1 Commits
c59da5ca98
...
f8f71a4f1c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8f71a4f1c |
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "machine",
|
|
||||||
"label": "machine",
|
|
||||||
"softwareType": "machine",
|
|
||||||
"suppliers": [
|
|
||||||
{
|
|
||||||
"id": "hidrostal",
|
|
||||||
"name": "Hidrostal",
|
|
||||||
"types": [
|
|
||||||
{
|
|
||||||
"id": "pump-centrifugal",
|
|
||||||
"name": "Centrifugal",
|
|
||||||
"models": [
|
|
||||||
{ "id": "hidrostal-H05K-S03R", "name": "hidrostal-H05K-S03R", "units": ["l/s","m3/h"] },
|
|
||||||
{ "id": "hidrostal-C5-D03R-SHN1", "name": "hidrostal-C5-D03R-SHN1", "units": ["l/s"] }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
34
datasets/assetData/rotatingmachine.json
Normal file
34
datasets/assetData/rotatingmachine.json
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"id": "rotatingmachine",
|
||||||
|
"label": "rotatingMachine",
|
||||||
|
"softwareType": "rotatingmachine",
|
||||||
|
"suppliers": [
|
||||||
|
{
|
||||||
|
"id": "hidrostal",
|
||||||
|
"name": "Hidrostal",
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"id": "pump-centrifugal",
|
||||||
|
"name": "Centrifugal",
|
||||||
|
"models": [
|
||||||
|
{
|
||||||
|
"id": "hidrostal-H05K-S03R",
|
||||||
|
"name": "hidrostal-H05K-S03R",
|
||||||
|
"units": [
|
||||||
|
"l/s",
|
||||||
|
"m3/h"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hidrostal-C5-D03R-SHN1",
|
||||||
|
"name": "hidrostal-C5-D03R-SHN1",
|
||||||
|
"units": [
|
||||||
|
"l/s"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -117,18 +117,24 @@ class ConfigManager {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add asset section if UI provides asset fields
|
// Asset section is emitted per-key: only fields the editor actually
|
||||||
if (uiConfig.supplier || uiConfig.category || uiConfig.assetType || uiConfig.model) {
|
// set propagate to the domain config. Schemas that omit a key (e.g.
|
||||||
config.asset = {
|
// rotatingMachine deliberately drops asset.supplier/category/type
|
||||||
uuid: uiConfig.uuid || uiConfig.assetUuid || null,
|
// because those come from the asset registry at runtime) no longer
|
||||||
tagCode: uiConfig.tagCode || uiConfig.assetTagCode || null,
|
// get those keys injected and then stripped by ValidationUtils with
|
||||||
supplier: uiConfig.supplier || 'Unknown',
|
// a warning. Empty strings from HTML defaults stay falsy → omitted →
|
||||||
category: uiConfig.category || 'sensor',
|
// schema default applies.
|
||||||
type: uiConfig.assetType || 'Unknown',
|
const asset = {};
|
||||||
model: uiConfig.model || 'Unknown',
|
const uuid = uiConfig.uuid || uiConfig.assetUuid;
|
||||||
unit: uiConfig.unit || 'unitless'
|
const tagCode = uiConfig.tagCode || uiConfig.assetTagCode;
|
||||||
};
|
if (uuid) asset.uuid = uuid;
|
||||||
}
|
if (tagCode) asset.tagCode = tagCode;
|
||||||
|
if (uiConfig.supplier) asset.supplier = uiConfig.supplier;
|
||||||
|
if (uiConfig.category) asset.category = uiConfig.category;
|
||||||
|
if (uiConfig.assetType) asset.type = uiConfig.assetType;
|
||||||
|
if (uiConfig.model) asset.model = uiConfig.model;
|
||||||
|
if (uiConfig.unit) asset.unit = uiConfig.unit;
|
||||||
|
if (Object.keys(asset).length > 0) config.asset = asset;
|
||||||
|
|
||||||
// Merge domain-specific sections. Must be a DEEP merge: domainConfig
|
// Merge domain-specific sections. Must be a DEEP merge: domainConfig
|
||||||
// commonly returns subsets of `general` / `asset` (e.g. {general:
|
// commonly returns subsets of `general` / `asset` (e.g. {general:
|
||||||
|
|||||||
@@ -91,6 +91,54 @@
|
|||||||
],
|
],
|
||||||
"description": "Defines the position of the measurement relative to its parent equipment or system."
|
"description": "Defines the position of the measurement relative to its parent equipment or system."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"distance": {
|
||||||
|
"default": null,
|
||||||
|
"rules": {
|
||||||
|
"type": "number",
|
||||||
|
"nullable": true,
|
||||||
|
"description": "Optional spatial offset from the parent equipment reference. Populated from the editor when hasDistance is enabled; null otherwise."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"distanceUnit": {
|
||||||
|
"default": "m",
|
||||||
|
"rules": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Unit for the functionality.distance offset (e.g. 'm', 'cm')."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"distanceDescription": {
|
||||||
|
"default": "",
|
||||||
|
"rules": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Free-text description of what the distance offset represents."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"process": {
|
||||||
|
"default": "process",
|
||||||
|
"rules": {
|
||||||
|
"type": "enum",
|
||||||
|
"values": [
|
||||||
|
{ "value": "process", "description": "Delta-compressed process message (default)." },
|
||||||
|
{ "value": "json", "description": "Raw JSON payload." },
|
||||||
|
{ "value": "csv", "description": "CSV-formatted payload." }
|
||||||
|
],
|
||||||
|
"description": "Format of the process payload emitted on output port 0."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dbase": {
|
||||||
|
"default": "influxdb",
|
||||||
|
"rules": {
|
||||||
|
"type": "enum",
|
||||||
|
"values": [
|
||||||
|
{ "value": "influxdb", "description": "InfluxDB line-protocol payload (default)." },
|
||||||
|
{ "value": "json", "description": "Raw JSON payload." },
|
||||||
|
{ "value": "csv", "description": "CSV-formatted payload." }
|
||||||
|
],
|
||||||
|
"description": "Format of the telemetry payload emitted on output port 1."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mode": {
|
"mode": {
|
||||||
@@ -107,10 +155,6 @@
|
|||||||
"value": "priorityControl",
|
"value": "priorityControl",
|
||||||
"description": "Machines are controlled sequentially from minimum to maximum output until each is maxed out, then additional machines are added."
|
"description": "Machines are controlled sequentially from minimum to maximum output until each is maxed out, then additional machines are added."
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"value": "prioritypercentagecontrol",
|
|
||||||
"description": "Machines are controlled sequentially from minimum to maximum output until each is maxed out, then additional machines are added based on a percentage of the total demand."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"value": "maintenance",
|
"value": "maintenance",
|
||||||
"description": "The group is in maintenance mode with limited actions (monitoring only)."
|
"description": "The group is in maintenance mode with limited actions (monitoring only)."
|
||||||
@@ -140,14 +184,6 @@
|
|||||||
"description": "Actions allowed in priorityControl mode."
|
"description": "Actions allowed in priorityControl mode."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"prioritypercentagecontrol": {
|
|
||||||
"default": ["statusCheck", "execSequentialControl", "balanceLoad", "emergencyStop"],
|
|
||||||
"rules": {
|
|
||||||
"type": "set",
|
|
||||||
"itemType": "string",
|
|
||||||
"description": "Actions allowed in manualOverride mode."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"maintenance": {
|
"maintenance": {
|
||||||
"default": ["statusCheck"],
|
"default": ["statusCheck"],
|
||||||
"rules": {
|
"rules": {
|
||||||
@@ -180,37 +216,10 @@
|
|||||||
"itemType": "string",
|
"itemType": "string",
|
||||||
"description": "Command sources allowed in priorityControl mode."
|
"description": "Command sources allowed in priorityControl mode."
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"prioritypercentagecontrol": {
|
|
||||||
"default": ["parent", "GUI", "physical", "API"],
|
|
||||||
"rules": {
|
|
||||||
"type": "set",
|
|
||||||
"itemType": "string",
|
|
||||||
"description": "Command sources allowed "
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Specifies the valid command sources recognized by the machine group controller for each mode."
|
"description": "Specifies the valid command sources recognized by the machine group controller for each mode."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"scaling": {
|
|
||||||
"current": {
|
|
||||||
"default": "normalized",
|
|
||||||
"rules": {
|
|
||||||
"type": "enum",
|
|
||||||
"values": [
|
|
||||||
{
|
|
||||||
"value": "normalized",
|
|
||||||
"description": "Scales the demand between 0–100% of the total flow capacity, interpolating to calculate the effective demand."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "absolute",
|
|
||||||
"description": "Uses the absolute demand value directly, capped between the min and max machine flow capacities."
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "The scaling mode for demand calculations."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,10 +96,37 @@
|
|||||||
"default": null,
|
"default": null,
|
||||||
"rules": {
|
"rules": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
|
"nullable": true,
|
||||||
"description": "Defines the position of the measurement relative to its parent equipment or system."
|
"description": "Defines the position of the measurement relative to its parent equipment or system."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"output": {
|
||||||
|
"process": {
|
||||||
|
"default": "process",
|
||||||
|
"rules": {
|
||||||
|
"type": "enum",
|
||||||
|
"values": [
|
||||||
|
{ "value": "process", "description": "Delta-compressed process message (default)." },
|
||||||
|
{ "value": "json", "description": "Raw JSON payload." },
|
||||||
|
{ "value": "csv", "description": "CSV-formatted payload." }
|
||||||
|
],
|
||||||
|
"description": "Format of the process payload emitted on output port 0."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dbase": {
|
||||||
|
"default": "influxdb",
|
||||||
|
"rules": {
|
||||||
|
"type": "enum",
|
||||||
|
"values": [
|
||||||
|
{ "value": "influxdb", "description": "InfluxDB line-protocol payload (default)." },
|
||||||
|
{ "value": "json", "description": "Raw JSON payload." },
|
||||||
|
{ "value": "csv", "description": "CSV-formatted payload." }
|
||||||
|
],
|
||||||
|
"description": "Format of the telemetry payload emitted on output port 1."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"asset": {
|
"asset": {
|
||||||
"uuid": {
|
"uuid": {
|
||||||
"default": null,
|
"default": null,
|
||||||
|
|||||||
@@ -55,7 +55,17 @@ class AssetMenu {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return keys[0];
|
// Previously fell back to keys[0] (alphabetically first category),
|
||||||
|
// which meant a softwareType mismatch silently showed the wrong asset
|
||||||
|
// tree — e.g. rotatingMachine (softwareType='rotatingmachine') with
|
||||||
|
// no matching registry file saw 'diffuser' models in the dropdown.
|
||||||
|
// Return null so the menu renders empty and the operator sees a clear
|
||||||
|
// 'No suppliers available' placeholder instead of a wrong category.
|
||||||
|
console.warn(
|
||||||
|
`[AssetMenu] No asset category matches softwareType='${this.softwareType}' or nodeName='${nodeName}'. ` +
|
||||||
|
`Available categories: [${keys.join(', ')}]. Menu will render empty.`
|
||||||
|
);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getAllMenuData(nodeName) {
|
getAllMenuData(nodeName) {
|
||||||
@@ -253,6 +263,26 @@ class AssetMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const suppliers = activeCategory ? activeCategory.suppliers : [];
|
const suppliers = activeCategory ? activeCategory.suppliers : [];
|
||||||
|
|
||||||
|
// The save handler intentionally discards node.supplier / node.assetType
|
||||||
|
// (denormalized copies of registry data — only node.model + node.unit
|
||||||
|
// are persisted identity). So on reopen we re-derive them from the
|
||||||
|
// saved model id by walking the registry tree. Without this the
|
||||||
|
// cascade always boots at "Select..." even when a model is saved.
|
||||||
|
if (node.model && (!node.supplier || !node.assetType)) {
|
||||||
|
for (const supplier of suppliers) {
|
||||||
|
const match = (supplier.types || []).find((type) =>
|
||||||
|
(type.models || []).some((model) =>
|
||||||
|
String(model.id || model.name) === String(node.model))
|
||||||
|
);
|
||||||
|
if (match) {
|
||||||
|
node.supplier = supplier.id || supplier.name;
|
||||||
|
node.assetType = match.id || match.name;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
populate(
|
populate(
|
||||||
elems.supplier,
|
elems.supplier,
|
||||||
suppliers,
|
suppliers,
|
||||||
|
|||||||
@@ -71,14 +71,22 @@ class Predict {
|
|||||||
// Capture share-source BEFORE config validation strips it (ConfigUtils
|
// Capture share-source BEFORE config validation strips it (ConfigUtils
|
||||||
// mutates the input config to drop unknown keys, which would remove
|
// mutates the input config to drop unknown keys, which would remove
|
||||||
// shareInputsFrom because it's not in predictConfig.json's schema).
|
// shareInputsFrom because it's not in predictConfig.json's schema).
|
||||||
|
// Detach on a shallow clone so validateSchema doesn't see the key at all
|
||||||
|
// — leaving it on the input would emit a `[interpolation] Unknown key
|
||||||
|
// 'shareInputsFrom'` warning per group-predictor on every curve refresh.
|
||||||
const _sharedSource = (config && config.shareInputsFrom instanceof Predict)
|
const _sharedSource = (config && config.shareInputsFrom instanceof Predict)
|
||||||
? config.shareInputsFrom
|
? config.shareInputsFrom
|
||||||
: null;
|
: null;
|
||||||
|
let _initConfig = config;
|
||||||
|
if (_initConfig && 'shareInputsFrom' in _initConfig) {
|
||||||
|
_initConfig = { ..._initConfig };
|
||||||
|
delete _initConfig.shareInputsFrom;
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize dependencies
|
// Initialize dependencies
|
||||||
this.emitter = new EventEmitter(); // Own EventEmitter
|
this.emitter = new EventEmitter(); // Own EventEmitter
|
||||||
this.configUtils = new ConfigUtils(defaultConfig);
|
this.configUtils = new ConfigUtils(defaultConfig);
|
||||||
this.config = this.configUtils.initConfig(config);
|
this.config = this.configUtils.initConfig(_initConfig);
|
||||||
|
|
||||||
// Init after config is set
|
// Init after config is set
|
||||||
this.logger = new Logger(this.config.general.logging.enabled,this.config.general.logging.logLevel, this.config.general.name);
|
this.logger = new Logger(this.config.general.logging.enabled,this.config.general.logging.logLevel, this.config.general.name);
|
||||||
|
|||||||
Reference in New Issue
Block a user