2025-06-25 17:27:32 +02:00
{
"general" : {
"name" : {
"default" : "Rotating Machine" ,
"rules" : {
"type" : "string" ,
"description" : "A human-readable name or label for this machine configuration."
}
} ,
"id" : {
"default" : null ,
"rules" : {
"type" : "string" ,
"nullable" : true ,
"description" : "A unique identifier for this configuration. If not provided, defaults to null."
}
} ,
"unit" : {
2025-11-30 09:24:29 +01:00
"default" : "l/s" ,
2025-06-25 17:27:32 +02:00
"rules" : {
"type" : "string" ,
"description" : "The default measurement unit for this configuration (e.g., 'meters', 'seconds', 'unitless')."
}
} ,
"logging" : {
"logLevel" : {
"default" : "info" ,
"rules" : {
"type" : "enum" ,
"values" : [
{
"value" : "debug" ,
"description" : "Log messages are printed for debugging purposes."
} ,
{
"value" : "info" ,
"description" : "Informational messages are printed."
} ,
{
"value" : "warn" ,
"description" : "Warning messages are printed."
} ,
{
"value" : "error" ,
"description" : "Error messages are printed."
}
]
}
} ,
"enabled" : {
"default" : true ,
"rules" : {
"type" : "boolean" ,
"description" : "Indicates whether logging is active. If true, log messages will be generated."
}
}
}
} ,
"functionality" : {
"softwareType" : {
2026-03-11 15:06:17 +01:00
"default" : "rotatingmachine" ,
2025-06-25 17:27:32 +02:00
"rules" : {
"type" : "string" ,
"description" : "Specified software type for this configuration."
}
} ,
"role" : {
"default" : "RotationalDeviceController" ,
"rules" : {
"type" : "string" ,
"description" : "Indicates the role this configuration plays within the system."
}
} ,
"positionVsParent" : {
"default" : "atEquipment" ,
"rules" : {
"type" : "enum" ,
"values" : [
{
"value" : "atEquipment" ,
"description" : "The node is connected at the equipment level and is responsible for controlling or monitoring the equipment as a whole."
} ,
{
"value" : "upstream" ,
"description" : "The node is connected in a downstream position, indicating it is responsible for monitoring or controlling processes that occur after the equipment's operation, such as product flow or output."
} ,
{
"value" : "downstream" ,
"description" : "The node is connected in an upstream position, indicating it is responsible for monitoring or controlling processes that occur before the equipment's operation, such as input flow or supply."
}
] ,
"description" : "Defines the position of the measurement relative to its parent equipment or system."
}
2026-04-13 13:21:18 +02:00
} ,
"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 (e.g. 'cable length from control panel to motor')."
}
}
} ,
"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."
2025-06-25 17:27:32 +02:00
}
2026-04-13 13:21:18 +02:00
} ,
"dbase" : {
"default" : "influxdb" ,
"rules" : {
"type" : "enum" ,
"values" : [
{ "value" : "influxdb" , "description" : "InfluxDB line-protocol payload (default)." } ,
2026-05-21 15:06:39 +02:00
{ "value" : "frost" , "description" : "FROST/SensorThings CoreSync payload." } ,
2026-04-13 13:21:18 +02:00
{ "value" : "json" , "description" : "Raw JSON payload." } ,
{ "value" : "csv" , "description" : "CSV-formatted payload." }
] ,
"description" : "Format of the telemetry payload emitted on output port 1."
}
}
2025-06-25 17:27:32 +02:00
} ,
"asset" : {
"uuid" : {
"default" : null ,
"rules" : {
"type" : "string" ,
"nullable" : true ,
"description" : "A universally unique identifier for this asset. May be null if not assigned."
}
} ,
2025-07-01 17:05:09 +02:00
"tagCode" : {
"default" : null ,
"rules" : {
"type" : "string" ,
"nullable" : true ,
"description" : "Asset tag code which is a unique identifier for this asset. May be null if not assigned."
}
} ,
2026-03-11 11:13:05 +01:00
"tagNumber" : {
"default" : null ,
"rules" : {
"type" : "string" ,
"nullable" : true ,
"description" : "Optional asset tag number for legacy integrations."
}
} ,
2025-06-25 17:27:32 +02:00
"geoLocation" : {
"default" : { } ,
"rules" : {
"type" : "object" ,
"description" : "An object representing the asset's physical coordinates or location." ,
"schema" : {
"x" : {
"default" : 0 ,
"rules" : {
"type" : "number" ,
"description" : "X coordinate of the asset's location."
}
} ,
"y" : {
"default" : 0 ,
"rules" : {
"type" : "number" ,
"description" : "Y coordinate of the asset's location."
}
} ,
"z" : {
"default" : 0 ,
"rules" : {
"type" : "number" ,
"description" : "Z coordinate of the asset's location."
}
}
}
}
} ,
"model" : {
feat(registry): AssetResolver + diffuser supplier curves (Jäger / Aerostrip / PIK / PRK)
Two related changes bundled together because the diffuser curve files
only make sense once the registry namespace they live in exists.
src/registry — new asset-metadata resolver:
- AssetResolver with synchronous resolve(namespace, id) + lazy cache,
async refresh() for future remote pulls.
- FileBackend (per-id or single-file layouts, case-insensitive) and a
stub HttpBackend (disabled unless EVOLV_ASSET_REMOTE=1).
- Namespaces: curves, menu, monsterSamples, monsterSpecs, units. Menu
namespace re-keys by inner softwareType + filename so editors that
pass either string resolve to the same tree.
- README explains how to add a namespace.
- AssetCategoryManager (datasets/assetData/index.js) becomes a thin
facade over the resolver so existing consumers don't move.
- 246/246 tests pass — including the 39-test registry suite.
datasets/assetData — file moves + new diffuser data:
- modelData/*.json deleted; curves/*.json is the canonical home.
- New diffuser.json menu tree with GVA, Jäger, Aquaconsult/Entec,
PIK/PRK suppliers.
- gva-elastox-r.json migrated from the inline _loadSpecs hardcode,
re-tagged coverageBasis="bottom-coverage-pct" (the legacy 2.4
elements/m² was a prior mis-conversion; we can't recover the
original % so it's a single-point curve under key "0").
- jaeger-jetflex-td-65-2-g-epdm-1000.json — extracted from the Jäger
EPDM-1000mm SSOTE/DWP chart on the data sheet (vector-PDF read).
SSOTE 8.20→6.40 %/m, DWP 25→48 mbar across Q 2-12 Nm³/h. Single
coverage (vendor doesn't state test conditions).
- aerostrip-phoenix.json — 4-coverage SOTE family at 4.75 m water
depth (DD 5/10/15/20 %, flux 10-70 Nm³/h·m²) from the Entec/de
Winter 2023-11-22 dataset; DWP curve from the 21 % @ 4.05 m chart.
- pik300.json / prk300.json — 5-coverage SOTE + SSOTR (DD 5-25 %)
with split DWP per model variant, water depth ≈ 4.0 m inferred from
the SOTE↔SSOTR ratio in the source spreadsheet.
src/configs/diffuser.json:
- New asset.{model, assetTagNumber} block so the editor's selected
model id survives validation.
- diffuser.density description corrected to "Bottom coverage [%]";
default 2.4 → 15 (typical fine-bubble install).
src/configs/{rotatingMachine,valve}.json: small alignment edits that
came with the registry phase.
src/menu/asset.js + src/menu/aquonSamples.js: rewritten as facades
over assetResolver, keeping the editor-side cascade behaviour intact.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:12:13 +02:00
"default" : null ,
2025-06-25 17:27:32 +02:00
"rules" : {
"type" : "string" ,
feat(registry): AssetResolver + diffuser supplier curves (Jäger / Aerostrip / PIK / PRK)
Two related changes bundled together because the diffuser curve files
only make sense once the registry namespace they live in exists.
src/registry — new asset-metadata resolver:
- AssetResolver with synchronous resolve(namespace, id) + lazy cache,
async refresh() for future remote pulls.
- FileBackend (per-id or single-file layouts, case-insensitive) and a
stub HttpBackend (disabled unless EVOLV_ASSET_REMOTE=1).
- Namespaces: curves, menu, monsterSamples, monsterSpecs, units. Menu
namespace re-keys by inner softwareType + filename so editors that
pass either string resolve to the same tree.
- README explains how to add a namespace.
- AssetCategoryManager (datasets/assetData/index.js) becomes a thin
facade over the resolver so existing consumers don't move.
- 246/246 tests pass — including the 39-test registry suite.
datasets/assetData — file moves + new diffuser data:
- modelData/*.json deleted; curves/*.json is the canonical home.
- New diffuser.json menu tree with GVA, Jäger, Aquaconsult/Entec,
PIK/PRK suppliers.
- gva-elastox-r.json migrated from the inline _loadSpecs hardcode,
re-tagged coverageBasis="bottom-coverage-pct" (the legacy 2.4
elements/m² was a prior mis-conversion; we can't recover the
original % so it's a single-point curve under key "0").
- jaeger-jetflex-td-65-2-g-epdm-1000.json — extracted from the Jäger
EPDM-1000mm SSOTE/DWP chart on the data sheet (vector-PDF read).
SSOTE 8.20→6.40 %/m, DWP 25→48 mbar across Q 2-12 Nm³/h. Single
coverage (vendor doesn't state test conditions).
- aerostrip-phoenix.json — 4-coverage SOTE family at 4.75 m water
depth (DD 5/10/15/20 %, flux 10-70 Nm³/h·m²) from the Entec/de
Winter 2023-11-22 dataset; DWP curve from the 21 % @ 4.05 m chart.
- pik300.json / prk300.json — 5-coverage SOTE + SSOTR (DD 5-25 %)
with split DWP per model variant, water depth ≈ 4.0 m inferred from
the SOTE↔SSOTR ratio in the source spreadsheet.
src/configs/diffuser.json:
- New asset.{model, assetTagNumber} block so the editor's selected
model id survives validation.
- diffuser.density description corrected to "Bottom coverage [%]";
default 2.4 → 15 (typical fine-bubble install).
src/configs/{rotatingMachine,valve}.json: small alignment edits that
came with the registry phase.
src/menu/asset.js + src/menu/aquonSamples.js: rewritten as facades
over assetResolver, keeping the editor-side cascade behaviour intact.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:12:13 +02:00
"nullable" : true ,
"description" : "Product model id (e.g. 'hidrostal-H05K-S03R'). Required at startup: the node looks the curve up via assetResolver.resolve('curves', model). Supplier/type/units are derived from the asset registry (assetResolver.resolveAssetMetadata) — do NOT save them on the node."
2025-06-25 17:27:32 +02:00
}
} ,
2025-07-01 17:05:09 +02:00
"unit" : {
feat(registry): AssetResolver + diffuser supplier curves (Jäger / Aerostrip / PIK / PRK)
Two related changes bundled together because the diffuser curve files
only make sense once the registry namespace they live in exists.
src/registry — new asset-metadata resolver:
- AssetResolver with synchronous resolve(namespace, id) + lazy cache,
async refresh() for future remote pulls.
- FileBackend (per-id or single-file layouts, case-insensitive) and a
stub HttpBackend (disabled unless EVOLV_ASSET_REMOTE=1).
- Namespaces: curves, menu, monsterSamples, monsterSpecs, units. Menu
namespace re-keys by inner softwareType + filename so editors that
pass either string resolve to the same tree.
- README explains how to add a namespace.
- AssetCategoryManager (datasets/assetData/index.js) becomes a thin
facade over the resolver so existing consumers don't move.
- 246/246 tests pass — including the 39-test registry suite.
datasets/assetData — file moves + new diffuser data:
- modelData/*.json deleted; curves/*.json is the canonical home.
- New diffuser.json menu tree with GVA, Jäger, Aquaconsult/Entec,
PIK/PRK suppliers.
- gva-elastox-r.json migrated from the inline _loadSpecs hardcode,
re-tagged coverageBasis="bottom-coverage-pct" (the legacy 2.4
elements/m² was a prior mis-conversion; we can't recover the
original % so it's a single-point curve under key "0").
- jaeger-jetflex-td-65-2-g-epdm-1000.json — extracted from the Jäger
EPDM-1000mm SSOTE/DWP chart on the data sheet (vector-PDF read).
SSOTE 8.20→6.40 %/m, DWP 25→48 mbar across Q 2-12 Nm³/h. Single
coverage (vendor doesn't state test conditions).
- aerostrip-phoenix.json — 4-coverage SOTE family at 4.75 m water
depth (DD 5/10/15/20 %, flux 10-70 Nm³/h·m²) from the Entec/de
Winter 2023-11-22 dataset; DWP curve from the 21 % @ 4.05 m chart.
- pik300.json / prk300.json — 5-coverage SOTE + SSOTR (DD 5-25 %)
with split DWP per model variant, water depth ≈ 4.0 m inferred from
the SOTE↔SSOTR ratio in the source spreadsheet.
src/configs/diffuser.json:
- New asset.{model, assetTagNumber} block so the editor's selected
model id survives validation.
- diffuser.density description corrected to "Bottom coverage [%]";
default 2.4 → 15 (typical fine-bubble install).
src/configs/{rotatingMachine,valve}.json: small alignment edits that
came with the registry phase.
src/menu/asset.js + src/menu/aquonSamples.js: rewritten as facades
over assetResolver, keeping the editor-side cascade behaviour intact.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:12:13 +02:00
"default" : null ,
2025-07-01 17:05:09 +02:00
"rules" : {
"type" : "string" ,
feat(registry): AssetResolver + diffuser supplier curves (Jäger / Aerostrip / PIK / PRK)
Two related changes bundled together because the diffuser curve files
only make sense once the registry namespace they live in exists.
src/registry — new asset-metadata resolver:
- AssetResolver with synchronous resolve(namespace, id) + lazy cache,
async refresh() for future remote pulls.
- FileBackend (per-id or single-file layouts, case-insensitive) and a
stub HttpBackend (disabled unless EVOLV_ASSET_REMOTE=1).
- Namespaces: curves, menu, monsterSamples, monsterSpecs, units. Menu
namespace re-keys by inner softwareType + filename so editors that
pass either string resolve to the same tree.
- README explains how to add a namespace.
- AssetCategoryManager (datasets/assetData/index.js) becomes a thin
facade over the resolver so existing consumers don't move.
- 246/246 tests pass — including the 39-test registry suite.
datasets/assetData — file moves + new diffuser data:
- modelData/*.json deleted; curves/*.json is the canonical home.
- New diffuser.json menu tree with GVA, Jäger, Aquaconsult/Entec,
PIK/PRK suppliers.
- gva-elastox-r.json migrated from the inline _loadSpecs hardcode,
re-tagged coverageBasis="bottom-coverage-pct" (the legacy 2.4
elements/m² was a prior mis-conversion; we can't recover the
original % so it's a single-point curve under key "0").
- jaeger-jetflex-td-65-2-g-epdm-1000.json — extracted from the Jäger
EPDM-1000mm SSOTE/DWP chart on the data sheet (vector-PDF read).
SSOTE 8.20→6.40 %/m, DWP 25→48 mbar across Q 2-12 Nm³/h. Single
coverage (vendor doesn't state test conditions).
- aerostrip-phoenix.json — 4-coverage SOTE family at 4.75 m water
depth (DD 5/10/15/20 %, flux 10-70 Nm³/h·m²) from the Entec/de
Winter 2023-11-22 dataset; DWP curve from the 21 % @ 4.05 m chart.
- pik300.json / prk300.json — 5-coverage SOTE + SSOTR (DD 5-25 %)
with split DWP per model variant, water depth ≈ 4.0 m inferred from
the SOTE↔SSOTR ratio in the source spreadsheet.
src/configs/diffuser.json:
- New asset.{model, assetTagNumber} block so the editor's selected
model id survives validation.
- diffuser.density description corrected to "Bottom coverage [%]";
default 2.4 → 15 (typical fine-bubble install).
src/configs/{rotatingMachine,valve}.json: small alignment edits that
came with the registry phase.
src/menu/asset.js + src/menu/aquonSamples.js: rewritten as facades
over assetResolver, keeping the editor-side cascade behaviour intact.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:12:13 +02:00
"nullable" : true ,
"description" : "Deployment unit chosen by the user (e.g. 'm3/h'). Must appear in the registry's model.units list for this model. Validated at startup."
2025-07-01 17:05:09 +02:00
}
} ,
2026-03-11 11:13:05 +01:00
"curveUnits" : {
"default" : {
"pressure" : "mbar" ,
"flow" : "m3/h" ,
"power" : "kW" ,
"control" : "%"
} ,
"rules" : {
"type" : "object" ,
"schema" : {
"pressure" : {
"default" : "mbar" ,
"rules" : {
"type" : "string" ,
"description" : "Pressure unit used on the machine curve dimension axis."
}
} ,
"flow" : {
"default" : "m3/h" ,
"rules" : {
"type" : "string" ,
"description" : "Flow unit used in the machine curve output (nq.y)."
}
} ,
"power" : {
"default" : "kW" ,
"rules" : {
"type" : "string" ,
"description" : "Power unit used in the machine curve output (np.y)."
}
} ,
"control" : {
"default" : "%" ,
"rules" : {
"type" : "string" ,
"description" : "Control axis unit used in the curve x-dimension."
}
}
}
}
} ,
2025-06-25 17:27:32 +02:00
"accuracy" : {
"default" : null ,
"rules" : {
"type" : "number" ,
"nullable" : true ,
"description" : "The accuracy of the machine or sensor, typically as a percentage or absolute value."
}
} ,
"machineCurve" : {
"default" : {
2026-04-14 10:27:59 +02:00
"nq" : { } ,
"np" : { }
} ,
2025-06-25 17:27:32 +02:00
"rules" : {
"type" : "machineCurve" ,
"description" : "All machine curves must have a 'nq' and 'np' curve. nq stands for the flow curve, np stands for the power curve. Together they form the efficiency curve."
}
}
} ,
"mode" : {
"current" : {
"default" : "auto" ,
"rules" : {
"type" : "enum" ,
"values" : [
{
"value" : "auto" ,
"description" : "Machine accepts setpoints from a parent controller and runs autonomously."
} ,
{
"value" : "virtualControl" ,
"description" : "Controlled via GUI setpoints; ignores parent commands."
} ,
{
"value" : "fysicalControl" ,
"description" : "Controlled via physical buttons or switches; ignores external automated commands."
}
] ,
"description" : "The operational mode of the machine."
}
} ,
"allowedActions" : {
"default" : { } ,
"rules" : {
"type" : "object" ,
"schema" : {
"auto" : {
2025-11-05 17:15:32 +01:00
"default" : [
"statuscheck" ,
"execmovement" ,
"execsequence" ,
2025-11-13 19:39:48 +01:00
"flowmovement" ,
2025-11-05 17:15:32 +01:00
"emergencystop" ,
"entermaintenance"
] ,
2025-06-25 17:27:32 +02:00
"rules" : {
"type" : "set" ,
"itemType" : "string" ,
"description" : "Actions allowed in auto mode."
}
} ,
"virtualControl" : {
2025-11-05 17:15:32 +01:00
"default" : [
"statuscheck" ,
"execmovement" ,
2025-11-13 19:39:48 +01:00
"flowmovement" ,
2025-11-05 17:15:32 +01:00
"execsequence" ,
"emergencystop" ,
"exitmaintenance"
] ,
2025-06-25 17:27:32 +02:00
"rules" : {
"type" : "set" ,
"itemType" : "string" ,
"description" : "Actions allowed in virtualControl mode."
}
} ,
"fysicalControl" : {
2025-11-05 17:15:32 +01:00
"default" : [
"statuscheck" ,
"emergencystop" ,
"entermaintenance" ,
"exitmaintenance"
] ,
2025-06-25 17:27:32 +02:00
"rules" : {
"type" : "set" ,
"itemType" : "string" ,
"description" : "Actions allowed in fysicalControl mode."
}
2025-11-05 15:47:05 +01:00
}
2025-06-25 17:27:32 +02:00
}
} ,
"description" : "Information about valid command sources recognized by the machine."
2025-11-05 17:15:32 +01:00
} ,
2025-06-25 17:27:32 +02:00
"allowedSources" : {
"default" : { } ,
"rules" : {
"type" : "object" ,
"schema" : {
"auto" : {
"default" : [ "parent" , "GUI" , "fysical" ] ,
"rules" : {
"type" : "set" ,
"itemType" : "string" ,
"description" : "Sources allowed in auto mode."
}
} ,
"virtualControl" : {
"default" : [ "GUI" , "fysical" ] ,
"rules" : {
"type" : "set" ,
"itemType" : "string" ,
"description" : "Sources allowed in virtualControl mode."
}
} ,
"fysicalControl" : {
"default" : [ "fysical" ] ,
"rules" : {
"type" : "set" ,
"itemType" : "string" ,
"description" : "Sources allowed in fysicalControl mode."
}
}
} ,
"description" : "Information about valid command sources recognized by the machine."
}
2025-11-05 17:15:32 +01:00
}
2025-06-25 17:27:32 +02:00
} ,
"source" : {
"default" : "parent" ,
"rules" : {
"type" : "enum" ,
"values" : [
{
"value" : "parent" ,
"description" : "Commands are received from a parent controller."
} ,
{
"value" : "GUI" ,
"description" : "Commands are received from a graphical user interface."
} ,
{
"value" : "fysical" ,
"description" : "Commands are received from physical buttons or switches."
}
] ,
"description" : "Information about valid command sources recognized by the machine."
}
} ,
"sequences" : {
"default" : { } ,
"rules" : {
"type" : "object" ,
"schema" : {
"startup" : {
"default" : [ "starting" , "warmingup" , "operational" ] ,
"rules" : {
"type" : "set" ,
"itemType" : "string" ,
"description" : "Sequence of states for starting up the machine."
}
} ,
"shutdown" : {
"default" : [ "stopping" , "coolingdown" , "idle" ] ,
"rules" : {
"type" : "set" ,
"itemType" : "string" ,
"description" : "Sequence of states for shutting down the machine."
}
} ,
"emergencystop" : {
"default" : [ "emergencystop" , "off" ] ,
"rules" : {
"type" : "set" ,
"itemType" : "string" ,
"description" : "Sequence of states for an emergency stop."
}
} ,
"boot" : {
"default" : [ "idle" , "starting" , "warmingup" , "operational" ] ,
"rules" : {
"type" : "set" ,
"itemType" : "string" ,
"description" : "Sequence of states for booting up the machine."
}
2025-11-05 15:47:05 +01:00
} ,
"entermaintenance" : {
"default" : [ "stopping" , "coolingdown" , "idle" , "maintenance" ] ,
"rules" : {
"type" : "set" ,
"itemType" : "string" ,
"description" : "Sequence of states if the machine is running to put it in maintenance state"
}
} ,
"exitmaintenance" : {
"default" : [ "off" , "idle" ] ,
"rules" : {
"type" : "set" ,
"itemType" : "string" ,
"description" : "Sequence of states if the machine is running to put it in maintenance state"
}
2025-06-25 17:27:32 +02:00
}
}
} ,
"description" : "Predefined sequences of states for the machine."
} ,
2025-10-31 14:03:54 +01:00
"flowNumber" : {
"default" : 1 ,
"rules" : {
"type" : "number" ,
"nullable" : false ,
"description" : "Defines which effluent flow of the parent node to handle."
}
2025-06-25 17:27:32 +02:00
}
}
2026-03-11 11:13:05 +01:00