Compare commits

..

5 Commits

34 changed files with 251 additions and 4209 deletions

View File

@@ -66,6 +66,15 @@
"units": ["g/m³", "mol/m³"] "units": ["g/m³", "mol/m³"]
} }
] ]
},
{
"name": "Quantity (TSS)",
"models": [
{
"name": "VegaSolidsProbe",
"units": ["g/m³"]
}
]
} }
] ]
} }
@@ -83,12 +92,7 @@
{ {
"id": "hidrostal-pump-001", "id": "hidrostal-pump-001",
"name": "hidrostal-H05K-S03R", "name": "hidrostal-H05K-S03R",
"units": ["l/s"] "units": ["m³/h", "gpm", "l/min"]
},
{
"id": "hidrostal-pump-002",
"name": "hidrostal-C5-D03R-SHN1",
"units": ["l/s"]
} }
] ]
} }

View File

@@ -1,838 +0,0 @@
{
"np": {
"400": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
0.5953611390998625,
1.6935085477165994,
3.801139124304824,
7.367829525776738,
12.081735423116616
]
},
"500": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
0.5522732775894703,
1.8497068236812997,
3.801139124304824,
7.367829525776738,
12.081735423116616
]
},
"600": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
0.5522732775894703,
1.7497197821018213,
3.801139124304824,
7.367829525776738,
12.081735423116616
]
},
"700": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
0.5522732775894703,
1.788320579602724,
3.9982668237045984,
7.367829525776738,
12.081735423116616
]
},
"800": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
0.5522732775894703,
1.7824519364844427,
3.9885060367793064,
7.367829525776738,
12.081735423116616
]
},
"900": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
0.5522732775894703,
1.6934482683506376,
3.9879559558537054,
7.367829525776738,
12.081735423116616
]
},
"1000": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
0.5522732775894703,
1.6954385513069579,
4.0743508382926795,
7.422392692482345,
12.081735423116616
]
},
"1100": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
0.5522732775894703,
1.6920721090317592,
4.160745720731654,
7.596626714476177,
12.081735423116616
]
},
"1200": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
0.5522732775894703,
1.6920721090317592,
4.302551231007837,
7.637247864947884,
12.081735423116616
]
},
"1300": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
0.5522732775894703,
1.6920721090317592,
4.37557913990704,
7.773442147000839,
12.081735423116616
]
},
"1400": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
0.5522732775894703,
1.6920721090317592,
4.334434337766139,
7.940911352646818,
12.081735423116616
]
},
"1500": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
0.5522732775894703,
1.6920721090317592,
4.2327206586037995,
8.005238800611183,
12.254836577088351
]
},
"1600": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
0.5522732775894703,
1.6920721090317592,
4.195405588464695,
7.991827302945298,
12.423663269044452
]
},
"1700": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
0.5522732775894703,
1.6920721090317592,
14.255458319309813,
8.096768422220196,
12.584668380908582
]
},
"1800": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
0.5522732775894703,
1.6920721090317592,
3.8742719210788685,
31.54620347513727,
12.637080520201405
]
},
"1900": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
0.5522732775894703,
1.6920721090317592,
3.8742719210788685,
8.148423429611098,
12.74916725120127
]
},
"2000": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
0.5522732775894703,
1.6920721090317592,
3.8742719210788685,
8.146439484120116,
12.905178964345618
]
},
"2100": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
0.5522732775894703,
1.6920721090317592,
3.8742719210788685,
8.149576025637684,
13.006940917309247
]
},
"2200": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
0.5522732775894703,
1.6920721090317592,
3.8742719210788685,
8.126246430368305,
13.107503837410825
]
},
"2300": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
0.5522732775894703,
1.6920721090317592,
3.8742719210788685,
8.104379361635342,
13.223235973280122
]
},
"2400": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
0.5522732775894703,
1.6920721090317592,
3.8742719210788685,
8.135190080423746,
13.36128347785936
]
},
"2500": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
0.5522732775894703,
1.6920721090317592,
3.8742719210788685,
7.981219508598527,
13.473697427231842
]
},
"2600": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
0.5522732775894703,
1.6920721090317592,
3.8742719210788685,
7.863899404441271,
13.50303289156837
]
},
"2700": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
0.5522732775894703,
1.6920721090317592,
3.8742719210788685,
7.658860522528131,
13.485230880073107
]
},
"2800": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
0.5522732775894703,
1.6920721090317592,
3.8742719210788685,
7.44407948309266,
13.446135725634615
]
},
"2900": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
0.5522732775894703,
1.6920721090317592,
3.8742719210788685,
7.44407948309266,
13.413693596332184
]
}
},
"nq": {
"400": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
7.6803204433986965,
25.506609120436963,
35.4,
44.4,
52.5
]
},
"500": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
6.4,
22.622804921188227,
35.4,
44.4,
52.5
]
},
"600": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
6.4,
19.966301579194372,
35.4,
44.4,
52.5
]
},
"700": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
6.4,
17.430763940163832,
33.79508340848005,
44.4,
52.5
]
},
"800": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
6.4,
14.752921911234477,
31.71885034449889,
44.4,
52.5
]
},
"900": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
6.4,
11.854693031181021,
29.923046639543475,
44.4,
52.5
]
},
"1000": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
6.4,
9.549433913822687,
26.734189128096668,
43.96760750800311,
52.5
]
},
"1100": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
6.4,
9.500000000000002,
26.26933164936586,
42.23523193272671,
52.5
]
},
"1200": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
6.4,
9.500000000000002,
24.443114637042832,
40.57167959798151,
52.5
]
},
"1300": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
6.4,
9.500000000000002,
22.41596168949836,
39.04561852479495,
52.5
]
},
"1400": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
6.4,
9.500000000000002,
20.276864821170303,
37.557663261443224,
52.252852231224054
]
},
"1500": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
6.4,
9.500000000000002,
18.252772588147742,
35.9974418607538,
50.68604059588987
]
},
"1600": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
6.4,
9.500000000000002,
16.31441663648616,
34.51170378091407,
49.20153034100798
]
},
"1700": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
6.4,
9.500000000000002,
14.255458319309813,
33.043410795291045,
47.820213744181245
]
},
"1800": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
6.4,
9.500000000000002,
12.7,
31.54620347513727,
46.51705619739449
]
},
"1900": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
6.4,
9.500000000000002,
12.7,
29.986013742375484,
45.29506741639918
]
},
"2000": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
6.4,
9.500000000000002,
12.7,
28.432646044605782,
44.107822395271945
]
},
"2100": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
6.4,
9.500000000000002,
12.7,
26.892634464336055,
42.758175515158776
]
},
"2200": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
6.4,
9.500000000000002,
12.7,
25.270679127870263,
41.467063889795895
]
},
"2300": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
6.4,
9.500000000000002,
12.7,
23.531132157718837,
40.293041104955826
]
},
"2400": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
6.4,
9.500000000000002,
12.7,
21.815645106750623,
39.03109248860755
]
},
"2500": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
6.4,
9.500000000000002,
12.7,
20.34997949463564,
37.71320701654063
]
},
"2600": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
6.4,
9.500000000000002,
12.7,
18.81710568651804,
36.35563657017404
]
},
"2700": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
6.4,
9.500000000000002,
12.7,
17.259072160217805,
35.02979557646653
]
},
"2800": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
6.4,
9.500000000000002,
12.7,
16,
33.74372254979665
]
},
"2900": {
"x": [
0,
25.510204081632654,
51.020408163265309,
76.530612244897952,
100
],
"y": [
6.4,
9.500000000000002,
12.7,
16,
32.54934541379723
]
}
}
}

View File

@@ -13,7 +13,6 @@ const logger = require('./src/helper/logger.js');
const validation = require('./src/helper/validationUtils.js'); const validation = require('./src/helper/validationUtils.js');
const configUtils = require('./src/helper/configUtils.js'); const configUtils = require('./src/helper/configUtils.js');
const assertions = require('./src/helper/assertionUtils.js') const assertions = require('./src/helper/assertionUtils.js')
const coolprop = require('./src/coolprop-node/src/index.js');
// Domain-specific modules // Domain-specific modules
const { MeasurementContainer } = require('./src/measurements/index.js'); const { MeasurementContainer } = require('./src/measurements/index.js');
@@ -40,7 +39,6 @@ module.exports = {
MeasurementContainer, MeasurementContainer,
nrmse, nrmse,
state, state,
coolprop,
convert, convert,
MenuManager, MenuManager,
childRegistrationUtils, childRegistrationUtils,

View File

@@ -1,694 +0,0 @@
{
"general": {
"name": {
"default": "Pumping Station",
"rules": {
"type": "string",
"description": "A human-readable name or label for this pumping station configuration."
}
},
"id": {
"default": null,
"rules": {
"type": "string",
"nullable": true,
"description": "A unique identifier for this pumping station configuration. If not provided, defaults to null."
}
},
"unit": {
"default": "m3/h",
"rules": {
"type": "string",
"description": "The default flow unit used for reporting station throughput."
}
},
"logging": {
"logLevel": {
"default": "info",
"rules": {
"type": "enum",
"values": [
{
"value": "debug",
"description": "Log verbose diagnostic messages that aid in troubleshooting the station."
},
{
"value": "info",
"description": "Log general informational messages about station behavior."
},
{
"value": "warn",
"description": "Log warnings when station behavior deviates from expected ranges."
},
{
"value": "error",
"description": "Log only error level messages for critical failures."
}
],
"description": "Defines the minimum severity that will be written to the log."
}
},
"enabled": {
"default": true,
"rules": {
"type": "boolean",
"description": "If true, logging is active for the pumping station node."
}
}
}
},
"functionality": {
"softwareType": {
"default": "pumpingStation",
"rules": {
"type": "string",
"description": "Specified software type used to locate the proper default configuration."
}
},
"role": {
"default": "StationController",
"rules": {
"type": "string",
"description": "Describes the station's function within the EVOLV ecosystem."
}
},
"positionVsParent": {
"default": "atEquipment",
"rules": {
"type": "enum",
"description": "Defines how the station is positioned relative to its parent process or site.",
"values": [
{
"value": "atEquipment",
"description": "The station is controlled at the equipment level and represents the primary pumping asset."
},
{
"value": "upstream",
"description": "The station governs flows entering upstream of the parent asset."
},
{
"value": "downstream",
"description": "The station influences conditions downstream of the parent asset, such as discharge or transfer."
}
]
}
},
"tickIntervalMs": {
"default": 1000,
"rules": {
"type": "number",
"min": 100,
"description": "Interval in milliseconds between internal evaluation cycles and output refreshes."
}
},
"supportsSimulation": {
"default": true,
"rules": {
"type": "boolean",
"description": "Indicates whether the station can operate using simulated inflow and level data."
}
},
"supportedChildSoftwareTypes": {
"default": [
"measurement"
],
"rules": {
"type": "set",
"itemType": "string",
"description": "List of child node software types that may register with the station."
}
}
},
"asset": {
"uuid": {
"default": null,
"rules": {
"type": "string",
"nullable": true,
"description": "Asset tag number which is a universally unique identifier for this pumping station."
}
},
"tagCode": {
"default": null,
"rules": {
"type": "string",
"nullable": true,
"description": "Asset tag code which uniquely identifies the pumping station. May be null if not assigned."
}
},
"category": {
"default": "station",
"rules": {
"type": "enum",
"values": [
{
"value": "station",
"description": "Represents a dedicated pumping station asset."
}
],
"description": "High level classification for asset reporting."
}
},
"type": {
"default": "pumpingStation",
"rules": {
"type": "string",
"description": "Specific asset type used to identify this configuration."
}
},
"model": {
"default": "Unknown",
"rules": {
"type": "string",
"description": "Manufacturer or integrator model designation for the station."
}
},
"supplier": {
"default": "Unknown",
"rules": {
"type": "string",
"description": "Primary supplier or maintainer responsible for the station."
}
},
"geoLocation": {
"default": {
"x": 0,
"y": 0,
"z": 0
},
"rules": {
"type": "object",
"description": "Coordinate reference for locating the pumping station.",
"schema": {
"x": {
"default": 0,
"rules": {
"type": "number",
"description": "X coordinate in meters or site units."
}
},
"y": {
"default": 0,
"rules": {
"type": "number",
"description": "Y coordinate in meters or site units."
}
},
"z": {
"default": 0,
"rules": {
"type": "number",
"description": "Z coordinate in meters or site units."
}
}
}
}
}
},
"basin": {
"volume": {
"default": "1",
"rules": {
"type": "number",
"description": "Total volume of empty basin in m3"
}
},
"height": {
"default": "1",
"rules": {
"type": "number",
"description": "Total height of basin in m"
}
},
"levelUnit": {
"default": "m",
"rules": {
"type": "string",
"description": "Unit used for level related setpoints and thresholds."
}
},
"heightInlet": {
"default": 2,
"rules": {
"type": "number",
"min": 0,
"description": "Height of the inlet pipe measured from the basin floor (m)."
}
},
"heightOutlet": {
"default": 0.2,
"rules": {
"type": "number",
"min": 0,
"description": "Height of the outlet pipe measured from the basin floor (m)."
}
},
"heightOverflow": {
"default": 2.5,
"rules": {
"type": "number",
"min": 0,
"description": "Height of the overflow point measured from the basin floor (m)."
}
},
"inletPipeDiameter": {
"default": 0.4,
"rules": {
"type": "number",
"min": 0,
"description": "Nominal inlet pipe diameter (m)."
}
},
"outletPipeDiameter": {
"default": 0.4,
"rules": {
"type": "number",
"min": 0,
"description": "Nominal outlet pipe diameter (m)."
}
}
},
"hydraulics": {
"maxInflowRate": {
"default": 200,
"rules": {
"type": "number",
"min": 0,
"description": "Maximum expected inflow during peak events (m3/h)."
}
},
"refHeight": {
"default": "NAP",
"rules": {
"type": "enum",
"values": [
{
"value": "NAP",
"description": "NAP (Normaal Amsterdams Peil)"
},
{
"value": "EVRF",
"description": "EVRF (European Vertical Reference Frame)"
},
{
"value": "EGM2008",
"description": "EGM2008 / EGM96 (satellietmetingen) Geopotentieel model earth "
}
],
"description": "Reference height to use to identify the height vs other basins with. This will say something more about the expected pressure loss in m head"
}
},
"staticHead": {
"default": 12,
"rules": {
"type": "number",
"min": 0,
"description": "Static head between station suction and discharge point (m)."
}
},
"maxDischargeHead": {
"default": 24,
"rules": {
"type": "number",
"min": 0,
"description": "Maximum allowable discharge head before calling for alarms (m)."
}
},
"pipelineLength": {
"default": 80,
"rules": {
"type": "number",
"min": 0,
"description": "Length of the discharge pipeline considered in calculations (m)."
}
},
"defaultFluid": {
"default": "wastewater",
"rules": {
"type": "enum",
"values": [
{
"value": "wastewater",
"description": "The wet well is primarily cylindrical."
},
{
"value": "water",
"description": "The wet well is rectangular or box shaped."
}
]
}
},
"temperatureReferenceDegC": {
"default": 15,
"rules": {
"type": "number",
"description": "Reference fluid temperature for property lookups (degC)."
}
}
},
"control": {
"controlStrategy": {
"default": "levelBased",
"rules": {
"type": "enum",
"values": [
{
"value": "levelBased",
"description": "Lead and lag pumps are controlled by basin level thresholds."
},
{
"value": "pressureBased",
"description": "Pumps target a discharge pressure setpoint."
},
{
"value": "flowTracking",
"description": "Pumps modulate to match measured inflow or downstream demand."
},
{
"value": "manual",
"description": "Pumps are operated manually or by an external controller."
}
],
"description": "Primary control philosophy for pump actuation."
}
},
"levelSetpoints": {
"default": {
"startLeadPump": 1.2,
"stopLeadPump": 0.8,
"startLagPump": 1.8,
"stopLagPump": 1.4,
"alarmHigh": 2.3,
"alarmLow": 0.3
},
"rules": {
"type": "object",
"description": "Level thresholds that govern pump staging and alarms (m).",
"schema": {
"startLeadPump": {
"default": 1.2,
"rules": {
"type": "number",
"description": "Level that starts the lead pump."
}
},
"stopLeadPump": {
"default": 0.8,
"rules": {
"type": "number",
"description": "Level that stops the lead pump."
}
},
"startLagPump": {
"default": 1.8,
"rules": {
"type": "number",
"description": "Level that starts the lag pump."
}
},
"stopLagPump": {
"default": 1.4,
"rules": {
"type": "number",
"description": "Level that stops the lag pump."
}
},
"alarmHigh": {
"default": 2.3,
"rules": {
"type": "number",
"description": "High level alarm threshold."
}
},
"alarmLow": {
"default": 0.3,
"rules": {
"type": "number",
"description": "Low level alarm threshold."
}
}
}
}
},
"pressureSetpoint": {
"default": 250,
"rules": {
"type": "number",
"min": 0,
"description": "Target discharge pressure when operating in pressure control (kPa)."
}
},
"alarmDebounceSeconds": {
"default": 10,
"rules": {
"type": "number",
"min": 0,
"description": "Time a condition must persist before raising an alarm (seconds)."
}
},
"equalizationTargetPercent": {
"default": 60,
"rules": {
"type": "number",
"min": 0,
"max": 100,
"description": "Target fill percentage of the basin when operating in equalization mode."
}
},
"autoRestartAfterPowerLoss": {
"default": true,
"rules": {
"type": "boolean",
"description": "If true, pumps resume based on last known state after power restoration."
}
},
"manualOverrideTimeoutMinutes": {
"default": 30,
"rules": {
"type": "number",
"min": 0,
"description": "Duration after which a manual override expires automatically (minutes)."
}
},
"flowBalanceTolerance": {
"default": 5,
"rules": {
"type": "number",
"min": 0,
"description": "Allowable error between inflow and outflow before adjustments are triggered (m3/h)."
}
}
},
"alarms": {
"default": {
"highLevel": {
"enabled": true,
"threshold": 2.3,
"delaySeconds": 30,
"severity": "critical",
"acknowledgmentRequired": true
},
"lowLevel": {
"enabled": true,
"threshold": 0.2,
"delaySeconds": 15,
"severity": "warning",
"acknowledgmentRequired": false
}
},
"rules": {
"type": "object",
"description": "Alarm configuration for the pumping station.",
"schema": {
"highLevel": {
"default": {
"enabled": true,
"threshold": 2.3,
"delaySeconds": 30,
"severity": "critical",
"acknowledgmentRequired": true
},
"rules": {
"type": "object",
"schema": {
"enabled": {
"default": true,
"rules": {
"type": "boolean",
"description": "Enable or disable the high level alarm."
}
},
"threshold": {
"default": 2.3,
"rules": {
"type": "number",
"description": "Level threshold that triggers the high level alarm (m)."
}
},
"delaySeconds": {
"default": 30,
"rules": {
"type": "number",
"min": 0,
"description": "Delay before issuing the high level alarm (seconds)."
}
},
"severity": {
"default": "critical",
"rules": {
"type": "enum",
"values": [
{
"value": "info",
"description": "Informational notification."
},
{
"value": "warning",
"description": "Warning condition requiring attention."
},
{
"value": "critical",
"description": "Critical alarm requiring immediate intervention."
}
],
"description": "Severity associated with the high level alarm."
}
},
"acknowledgmentRequired": {
"default": true,
"rules": {
"type": "boolean",
"description": "If true, this alarm must be acknowledged by an operator."
}
}
}
}
},
"lowLevel": {
"default": {
"enabled": true,
"threshold": 0.2,
"delaySeconds": 15,
"severity": "warning",
"acknowledgmentRequired": false
},
"rules": {
"type": "object",
"schema": {
"enabled": {
"default": true,
"rules": {
"type": "boolean",
"description": "Enable or disable the low level alarm."
}
},
"threshold": {
"default": 0.2,
"rules": {
"type": "number",
"description": "Level threshold that triggers the low level alarm (m)."
}
},
"delaySeconds": {
"default": 15,
"rules": {
"type": "number",
"min": 0,
"description": "Delay before issuing the low level alarm (seconds)."
}
},
"severity": {
"default": "warning",
"rules": {
"type": "enum",
"values": [
{
"value": "info",
"description": "Informational notification."
},
{
"value": "warning",
"description": "Warning condition requiring attention."
},
{
"value": "critical",
"description": "Critical alarm requiring immediate intervention."
}
],
"description": "Severity associated with the low level alarm."
}
},
"acknowledgmentRequired": {
"default": false,
"rules": {
"type": "boolean",
"description": "If true, this alarm must be acknowledged by an operator."
}
}
}
}
}
}
}
},
"simulation": {
"enabled": {
"default": false,
"rules": {
"type": "boolean",
"description": "If true, the station operates in simulation mode using generated inflow and level data."
}
},
"mode": {
"default": "diurnal",
"rules": {
"type": "enum",
"values": [
{
"value": "static",
"description": "Use constant inflow and level conditions."
},
{
"value": "diurnal",
"description": "Use a typical diurnal inflow curve to drive simulation."
},
{
"value": "storm",
"description": "Use an elevated inflow profile representing a storm event."
}
],
"description": "Defines which synthetic profile drives the simulation."
}
},
"seed": {
"default": 42,
"rules": {
"type": "number",
"description": "Seed used for pseudo-random components in simulation."
}
},
"applyRandomNoise": {
"default": true,
"rules": {
"type": "boolean",
"description": "If true, adds small noise to simulated measurements."
}
},
"inflowProfile": {
"default": [
80,
110,
160,
120,
90
],
"rules": {
"type": "array",
"itemType": "number",
"minLength": 1,
"description": "Relative inflow profile used when mode is set to diurnal or storm (percentage of design inflow)."
}
}
}
}

View File

@@ -412,6 +412,14 @@
], ],
"description": "The frequency at which calculations are performed." "description": "The frequency at which calculations are performed."
} }
},
"flowNumber": {
"default": 1,
"rules": {
"type": "number",
"nullable": false,
"description": "Defines which effluent flow of the parent node to handle."
}
} }
} }

View File

@@ -1,2 +0,0 @@
# Auto detect text files and perform LF normalization
* text=auto

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2024 Craig Zych
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,253 +0,0 @@
# CoolProp-Node
A Node.js wrapper for CoolProp providing an easy-to-use interface for thermodynamic calculations and refrigerant properties. Unlike all the other CoolProp npm packages I've seen, this one should actually work. Please report any issues.
## Installation
```bash
npm install coolprop-node
```
## Features
- Easy-to-use async interface for CoolProp
- Unit conversion support (Temperature: K/C/F, Pressure: Pa/kPa/bar/psi)
- Automatic initialization
- Configurable defaults
- Comprehensive error handling
## Dependencies
No External Dependencies, as CoolProp.js and CoolProp.wasm are bundled with the package.
- [CoolProp](https://github.com/CoolProp/CoolProp) for the powerful thermodynamic library
## Quick Start
```javascript
const nodeprop = require('coolprop-node');
async function example() {
// Initialize with defaults (optional)
await nodeprop.init({
refrigerant: 'R404A',
tempUnit: 'C',
pressureUnit: 'bar'
});
// Calculate superheat
const result = await nodeprop.calculateSuperheat({
temperature: 25, // 25°C
pressure: 10, // 10 bar
refrigerant: 'R404A' // optional if set in init
});
console.log(result);
// expected output:
{
type: 'success',
superheat: 5.2,
saturationTemperature: 19.8,
refrigerant: 'R404A',
units: {
temperature: 'C',
pressure: 'bar'
}
}
}
example();
```
## API Reference
### nodeprop.init(config)
Initializes the wrapper with optional configuration.
###### Note: Calling `init()` is optional. The library will initialize automatically when you make your first call to any function, but you must provide a `refrigerant` parameter in that first call.
```javascript
await nodeprop.init({
refrigerant: 'R404A', // Required on first init
tempUnit: 'C', // Optional, defaults to 'K'
pressureUnit: 'bar' // Optional, defaults to 'Pa'
});
```
### nodeprop.calculateSuperheat(input)
Calculates superheat for a given refrigerant.
```javascript
const result = await nodeprop.calculateSuperheat({
temperature: 25, // 25°C
pressure: 10, // 10 bar
refrigerant: 'R404A' // optional if set in init
});
returns:
{
type: 'success',
superheat: 5.2,
saturationTemperature: 19.8,
refrigerant: 'R404A',
units: {
temperature: 'C',
pressure: 'bar'
}
}
```
### nodeprop.getSaturationTemperature(input)
Calculates saturation temperature for a given refrigerant.
```javascript
const result = await nodeprop.calculateSaturationTemperature({
temperature: 25, // 25°C
pressure: 10, // 10 bar
refrigerant: 'R404A' // optional if set in init
});
returns:
{
type: 'success',
temperature: 19.8,
refrigerant: 'R404A',
units: {
temperature: 'C',
pressure: 'bar'
}
}
```
### nodeprop.getSaturationPressure(input)
Calculates saturation pressure for a given refrigerant.
```javascript
const result = await nodeprop.calculateSaturationPressure({
temperature: 25, // 25°C
refrigerant: 'R404A' // optional if set in init
});
returns:
{
type: 'success',
pressure: 10,
refrigerant: 'R404A',
units: {
temperature: 'C',
pressure: 'bar'
}
}
```
### nodeprop.calculateSubcooling(input)
Calculates subcooling for a given refrigerant.
```javascript
const result = await nodeprop.calculateSubcooling({
temperature: 25, // 25°C
pressure: 10, // 10 bar
refrigerant: 'R404A' // optional if set in init
});
returns:
{
type: 'success',
subcooling: 5.2,
saturationTemperature: 19.8,
refrigerant: 'R404A',
units: {
temperature: 'C',
pressure: 'bar'
}
}
```
### nodeprop.calculateSuperheat(input)
Calculates superheat for a given refrigerant.
```javascript
const result = await nodeprop.calculateSuperheat({
temperature: 25, // 25°C
pressure: 10, // 10 bar
refrigerant: 'R404A' // optional if set in init
});
returns:
{
type: 'success',
superheat: 5.2,
saturationTemperature: 19.8,
refrigerant: 'R404A',
units: {
temperature: 'C',
pressure: 'bar'
}
}
```
### nodeprop.getProperties(input)
Gets all properties for a given refrigerant.
```javascript
const result = await nodeprop.getProperties({
temperature: 25, // 25°C
pressure: 10, // 10 bar
refrigerant: 'R404A' // optional if set in init
});
returns:
{
type: 'success',
properties: {
temperature: 25, // in configured temperature unit (e.g., °C)
pressure: 10, // in configured pressure unit (e.g., bar)
density: 1234.56, // in kg/m³
enthalpy: 400000, // in J/kg
entropy: 1750, // in J/kg/K
quality: 1, // dimensionless (0-1)
conductivity: 0.013, // in W/m/K
viscosity: 1.2e-5, // in Pa·s
specificHeat: 850 // in J/kg/K
},
refrigerant: 'R404A',
units: {
temperature: 'C',
pressure: 'bar',
density: 'kg/m³',
enthalpy: 'J/kg',
entropy: 'J/kg/K',
quality: 'dimensionless',
conductivity: 'W/m/K',
viscosity: 'Pa·s',
specificHeat: 'J/kg/K'
}
}
```
### nodeprop.PropsSI
Direct access to CoolProp's PropsSI function.
```javascript
const PropsSI = await nodeprop.getPropsSI();
const result = PropsSI('H', 'T', 298.15, 'P', 101325, 'R134a');
```
### Error Handling
```javascript
const result = await nodeprop.calculateSuperheat({
temperature: 25, // 25°C
pressure: 10, // 10 bar
refrigerant: 'R404' // Invalid refrigerant. Must be supported by CoolProp, but R404 is not even a valid refrigerant.
});
returns:
{
type: 'error',
message: 'Invalid refrigerant'
}
```
### Acknowledgements
- [CoolProp](https://github.com/CoolProp/CoolProp) for the powerful thermodynamic library

View File

@@ -1,80 +0,0 @@
const coolprop = require('./src/index.js');
// Function to generate random number between min and max
function getRandomNumber(min, max) {
return min + Math.random() * (max - min);
}
// Generate 1000 combinations of temperature and pressure
function generateCombinations(count) {
const combinations = [];
// For R744 (CO2), using realistic ranges from test files
// Temperature range: -40°F to 32°F
// Pressure range: 131 psig to 491 psig
for (let i = 0; i < count; i++) {
const temperature = getRandomNumber(-40, 32);
const pressure = getRandomNumber(131, 491);
combinations.push({
temperature,
pressure,
refrigerant: 'R744',
tempUnit: 'F',
pressureUnit: 'psig'
});
}
return combinations;
}
async function runBenchmark() {
console.log('Generating 1000 temperature and pressure combinations...');
const combinations = generateCombinations(1000);
console.log('Combinations generated.');
// Pre-initialize the library
console.log('Initializing library...');
await coolprop.init({
refrigerant: 'R744',
tempUnit: 'F',
pressureUnit: 'psig'
});
console.log('Library initialized.');
// Run benchmark
console.log('Starting benchmark...');
const startTime = performance.now();
const results = [];
for (let i = 0; i < combinations.length; i++) {
const result = await coolprop.calculateSuperheat(combinations[i]);
results.push(result);
// Show progress every 100 calculations
if ((i + 1) % 100 === 0) {
console.log(`Processed ${i + 1} / ${combinations.length} calculations`);
}
}
const endTime = performance.now();
const totalTime = endTime - startTime;
const avgTime = totalTime / combinations.length;
// Report results
console.log('\nBenchmark Results:');
console.log(`Total time: ${totalTime.toFixed(2)} ms`);
console.log(`Average time per calculation: ${avgTime.toFixed(2)} ms`);
console.log(`Calculations per second: ${(1000 / avgTime).toFixed(2)}`);
// Count success and error results
const successful = results.filter(r => r.type === 'success').length;
const failed = results.filter(r => r.type === 'error').length;
console.log(`\nSuccessful calculations: ${successful}`);
console.log(`Failed calculations: ${failed}`);
}
// Run the benchmark
runBenchmark().catch(error => {
console.error('Benchmark failed:', error);
});

File diff suppressed because one or more lines are too long

View File

@@ -1,31 +0,0 @@
{
"name": "coolprop-node",
"version": "1.0.20",
"main": "src/index.js",
"scripts": {
"test": "jest",
"test:watch": "jest --watch"
},
"keywords": [
"coolprop",
"thermodynamics",
"fluid properties",
"refrigerant",
"refrigeration",
"refprop"
],
"author": "Craig Zych",
"license": "MIT",
"description": "A Node.js wrapper for CoolProp providing an easy-to-use interface for thermodynamic calculations and refrigerant properties. Unlike all the other CoolProp npm packages I've seen, this one should actually work. Please report any issues. ",
"devDependencies": {
"jest": "^29.7.0"
},
"jest": {
"testEnvironment": "node",
"verbose": true
},
"repository": {
"type": "git",
"url": "https://github.com/Craigzyc/coolprop-node.git"
}
}

View File

@@ -1,92 +0,0 @@
// Load and configure the CoolProp module
const fs = require('fs');
const path = require('path');
const vm = require('vm');
// Mock XMLHttpRequest
class XMLHttpRequest {
open(method, url) {
this.method = method;
this.url = url;
}
send() {
try {
// Convert the URL to a local file path
const localPath = path.join(__dirname, '..', 'coolprop', path.basename(this.url));
const data = fs.readFileSync(localPath);
this.status = 200;
this.response = data;
this.responseType = 'arraybuffer';
if (this.onload) {
this.onload();
}
} catch (error) {
if (this.onerror) {
this.onerror(error);
}
}
}
}
// Read the coolprop.js file
const coolpropJs = fs.readFileSync(path.join(__dirname, '../coolprop/coolprop.js'), 'utf8');
// Create a context for the module
const context = {
window: {},
self: {},
Module: {
onRuntimeInitialized: function() {
context.Module.initialized = true;
}
},
importScripts: () => {},
console: console,
location: {
href: 'file://' + __dirname,
pathname: __dirname,
},
document: {
currentScript: { src: '' }
},
XMLHttpRequest: XMLHttpRequest
};
// Make self reference the context itself
context.self = context;
// Make window reference the context itself
context.window = context;
// Execute coolprop.js in our custom context
vm.createContext(context);
vm.runInContext(coolpropJs, context);
// Wait for initialization
function waitForInit(timeout = 5000) {
return new Promise((resolve, reject) => {
const start = Date.now();
const check = () => {
if (context.Module.initialized) {
resolve(context.Module);
} else if (Date.now() - start > timeout) {
reject(new Error('CoolProp initialization timed out'));
} else {
setTimeout(check, 100);
}
};
check();
});
}
module.exports = {
init: () => waitForInit(),
PropsSI: (...args) => {
if (!context.Module.initialized) {
throw new Error('CoolProp not initialized. Call init() first');
}
return context.Module.PropsSI(...args);
}
};

View File

@@ -1,419 +0,0 @@
const coolprop = require('./cp.js');
const customRefs = require('./refData.js');
class CoolPropWrapper {
constructor() {
this.initialized = false;
this.defaultRefrigerant = null;
this.defaultTempUnit = 'K'; // K, C, F
this.defaultPressureUnit = 'Pa' // Pa, kPa, bar, psi
this.customRef = false;
}
// Temperature conversion helpers
_convertTempToK(value, unit = this.defaultTempUnit) {
switch(unit.toUpperCase()) {
case 'K': return value;
case 'C': return value + 273.15;
case 'F': return (value + 459.67) * 5/9;
default: throw new Error('Unsupported temperature unit');
}
}
_convertTempFromK(value, unit = this.defaultTempUnit) {
switch(unit.toUpperCase()) {
case 'K': return value;
case 'C': return value - 273.15;
case 'F': return value * 9/5 - 459.67;
default: throw new Error('Unsupported temperature unit');
}
}
_convertDeltaTempFromK(value, unit = this.defaultTempUnit) {
switch(unit.toUpperCase()) {
case 'K': return value;
case 'C': return value;
case 'F': return (value * 1.8);
default: throw new Error('Unsupported temperature unit');
}
}
// Pressure conversion helpers
_convertPressureToPa(value, unit = this.defaultPressureUnit) {
switch(unit.toUpperCase()) {
case 'PAA': return value; // Absolute Pascal
case 'PAG':
case 'PA': return value + 101325; // Gauge Pascal
case 'KPAA': return value * 1000; // Absolute kiloPascal
case 'KPAG':
case 'KPA': return value * 1000 + 101325; // Gauge kiloPascal
case 'BARA': return value * 100000; // Absolute bar
case 'BARG':
case 'BAR': return value * 100000 + 101325; // Gauge bar
case 'PSIA': return value * 6894.76; // Absolute PSI
case 'PSIG':
case 'PSI': return value * 6894.76 + 101325;// Gauge PSI
default: throw new Error('Unsupported pressure unit');
}
}
_convertPressureFromPa(value, unit = this.defaultPressureUnit) {
switch(unit.toUpperCase()) {
case 'PAA': return value; // Absolute Pascal
case 'PAG':
case 'PA': return value - 101325; // Gauge Pascal
case 'KPAA': return value / 1000; // Absolute kiloPascal
case 'KPAG':
case 'KPA': return (value - 101325) / 1000; // Gauge kiloPascal
case 'BARA': return value / 100000; // Absolute bar
case 'BARG':
case 'BAR': return (value - 101325) / 100000;// Gauge bar
case 'PSIA': return value / 6894.76; // Absolute PSI
case 'PSIG':
case 'PSI': return (value - 101325) / 6894.76;// Gauge PSI
default: throw new Error('Unsupported pressure unit');
}
}
async init(config = {}) {
try {
// If already initialized, only update defaults if provided
if (this.initialized) {
if (config.refrigerant) this.defaultRefrigerant = config.refrigerant;
if (config.tempUnit) {
if (!['K', 'C', 'F'].includes(config.tempUnit.toUpperCase())) {
return { type: 'error', message: 'Invalid temperature unit. Must be K, C, or F' };
}
this.defaultTempUnit = config.tempUnit;
}
if (config.pressureUnit) {
if (!['PA', 'PAA', 'KPA', 'KPAA', 'BAR', 'BARA', 'PSI', 'PSIA'].includes(config.pressureUnit.toUpperCase())) {
return { type: 'error', message: 'Invalid pressure unit. Must be Pa, Paa, kPa, kPaa, bar, bara, psi, or psia' };
}
this.defaultPressureUnit = config.pressureUnit;
}
return { type: 'success', message: 'Default settings updated' };
}
// First time initialization
if (!config.refrigerant) {
throw new Error('Refrigerant must be specified during initialization');
}
// Validate temperature unit if provided
if (config.tempUnit && !['K', 'C', 'F'].includes(config.tempUnit.toUpperCase())) {
throw new Error('Invalid temperature unit. Must be K, C, or F');
}
// Validate pressure unit if provided
if (config.pressureUnit && !['PA', 'PAA', 'KPA', 'KPAA', 'BAR', 'BARA', 'PSI', 'PSIA'].includes(config.pressureUnit.toUpperCase())) {
throw new Error('Invalid pressure unit. Must be Pa, Paa, kPa, kPaa, bar, bara, psi, or psia');
}
await coolprop.init();
this.initialized = true;
this.defaultRefrigerant = config.refrigerant;
this.defaultTempUnit = config.tempUnit || this.defaultTempUnit;
this.defaultPressureUnit = config.pressureUnit || this.defaultPressureUnit;
return { type: 'success', message: 'Initialized successfully' };
} catch (error) {
return { type: 'error', message: error.message };
}
}
async _ensureInit(config = {}) {
// Initialize CoolProp if not already done
if (!this.initialized) {
if (!config.refrigerant && !this.defaultRefrigerant) {
throw new Error('Refrigerant must be specified either during initialization or in the method call');
}
await coolprop.init();
this.initialized = true;
}
// Validate temperature unit if provided
if (config.tempUnit && !['K', 'C', 'F'].includes(config.tempUnit.toUpperCase())) {
throw new Error('Invalid temperature unit. Must be K, C, or F');
}
// Validate pressure unit if provided
if (config.pressureUnit && !['PA', 'PAA', 'PAG', 'KPA', 'KPAA', 'KPAG', 'BAR', 'BARA', 'BARG', 'PSI', 'PSIA', 'PSIG'].includes(config.pressureUnit.toUpperCase())) {
throw new Error('Invalid pressure unit. Must be Pa, Paa, Pag, kPa, kPaa, kPag, bar, bara, barg, psi, psia, or psig');
}
// Validate refrigerant if provided
if (config.refrigerant && typeof config.refrigerant !== 'string') {
throw new Error('Invalid refrigerant type');
}
if (config.refrigerant && Object.keys(customRefs).includes(config.refrigerant)) {
this.customRef = true;
this.defaultRefrigerant = config.refrigerant;
//console.log(`Using custom refrigerant flag for ${this.defaultRefrigerant}`);
}else if(this.customRef && config.refrigerant){
this.customRef = false;
//console.log(`Cleared custom refrigerant flag`);
}
// Update instance variables with new config values if provided
if (config.refrigerant) this.defaultRefrigerant = config.refrigerant;
if (config.tempUnit) this.defaultTempUnit = config.tempUnit.toUpperCase();
if (config.pressureUnit) this.defaultPressureUnit = config.pressureUnit.toUpperCase();
}
async getConfig() {
return {
refrigerant: this.defaultRefrigerant,
tempUnit: this.defaultTempUnit,
pressureUnit: this.defaultPressureUnit
};
}
async setConfig(config) {
await this.init(config);
return {
type: 'success',
message: 'Config updated successfully',
config: await this.getConfig()
};
}
// Helper method for linear interpolation/extrapolation
_interpolateSaturationTemperature(pressurePa, saturationData, pressureType = 'liquid') {
const data = saturationData.sort((a, b) => a[pressureType] - b[pressureType]); // Sort by specified pressure type
// If pressure is below the lowest data point, extrapolate using first two points
if (pressurePa <= data[0][pressureType]) {
if (data.length < 2) return data[0].K;
const p1 = data[0], p2 = data[1];
const slope = (p2.K - p1.K) / (p2[pressureType] - p1[pressureType]);
return p1.K + slope * (pressurePa - p1[pressureType]);
}
// If pressure is above the highest data point, extrapolate using last two points
if (pressurePa >= data[data.length - 1][pressureType]) {
if (data.length < 2) return data[data.length - 1].K;
const p1 = data[data.length - 2], p2 = data[data.length - 1];
const slope = (p2.K - p1.K) / (p2[pressureType] - p1[pressureType]);
return p1.K + slope * (pressurePa - p1[pressureType]);
}
// Find the two adjacent points for interpolation
for (let i = 0; i < data.length - 1; i++) {
if (pressurePa >= data[i][pressureType] && pressurePa <= data[i + 1][pressureType]) {
const p1 = data[i], p2 = data[i + 1];
// Linear interpolation
const slope = (p2.K - p1.K) / (p2[pressureType] - p1[pressureType]);
return p1.K + slope * (pressurePa - p1[pressureType]);
}
}
// Fallback (shouldn't reach here)
return data[0].K;
}
// Helper method for linear interpolation/extrapolation of saturation pressure
_interpolateSaturationPressure(tempK, saturationData, pressureType = 'liquid') {
const data = saturationData.sort((a, b) => a.K - b.K); // Sort by temperature
// If temperature is below the lowest data point, extrapolate using first two points
if (tempK <= data[0].K) {
if (data.length < 2) return data[0][pressureType];
const p1 = data[0], p2 = data[1];
const slope = (p2[pressureType] - p1[pressureType]) / (p2.K - p1.K);
return p1[pressureType] + slope * (tempK - p1.K);
}
// If temperature is above the highest data point, extrapolate using last two points
if (tempK >= data[data.length - 1].K) {
if (data.length < 2) return data[data.length - 1][pressureType];
const p1 = data[data.length - 2], p2 = data[data.length - 1];
const slope = (p2[pressureType] - p1[pressureType]) / (p2.K - p1.K);
return p1[pressureType] + slope * (tempK - p1.K);
}
// Find the two adjacent points for interpolation
for (let i = 0; i < data.length - 1; i++) {
if (tempK >= data[i].K && tempK <= data[i + 1].K) {
const p1 = data[i], p2 = data[i + 1];
// Linear interpolation
const slope = (p2[pressureType] - p1[pressureType]) / (p2.K - p1.K);
return p1[pressureType] + slope * (tempK - p1.K);
}
}
// Fallback (shouldn't reach here)
return data[0][pressureType];
}
async getSaturationTemperature({ pressure, refrigerant = this.defaultRefrigerant, pressureUnit = this.defaultPressureUnit, tempUnit = this.defaultTempUnit }) {
try {
await this._ensureInit({ refrigerant, pressureUnit, tempUnit });
const pressurePa = this._convertPressureToPa(pressure, pressureUnit);
let tempK;
if(this.customRef){
tempK = this._interpolateSaturationTemperature(pressurePa, customRefs[refrigerant].saturation);
}else{
tempK = coolprop.PropsSI('T', 'P', pressurePa, 'Q', 0, this.customRefString || refrigerant);
}
return {
type: 'success',
temperature: this._convertTempFromK(tempK, tempUnit),
refrigerant,
units: {
temperature: tempUnit,
pressure: pressureUnit
}
};
} catch (error) {
return { type: 'error', message: error.message };
}
}
async getSaturationPressure({ temperature, refrigerant = this.defaultRefrigerant, tempUnit = this.defaultTempUnit, pressureUnit = this.defaultPressureUnit }) {
try {
await this._ensureInit({ refrigerant, tempUnit, pressureUnit });
const tempK = this._convertTempToK(temperature, tempUnit);
let pressurePa;
if(this.customRef){
pressurePa = this._interpolateSaturationPressure(tempK, customRefs[refrigerant].saturation);
}else{
pressurePa = coolprop.PropsSI('P', 'T', tempK, 'Q', 0, this.customRefString || refrigerant);
}
return {
type: 'success',
pressure: this._convertPressureFromPa(pressurePa, pressureUnit),
refrigerant,
units: {
temperature: tempUnit,
pressure: pressureUnit
}
};
} catch (error) {
return { type: 'error', message: error.message };
}
}
async calculateSubcooling({ temperature, pressure, refrigerant = this.defaultRefrigerant, tempUnit = this.defaultTempUnit, pressureUnit = this.defaultPressureUnit }) {
try {
await this._ensureInit({ refrigerant, tempUnit, pressureUnit });
const tempK = this._convertTempToK(temperature, tempUnit);
const pressurePa = this._convertPressureToPa(pressure, pressureUnit);
let satTempK;
if(this.customRef){
// Use liquid pressure for subcooling
satTempK = this._interpolateSaturationTemperature(pressurePa, customRefs[refrigerant].saturation, 'liquid');
}else{
satTempK = coolprop.PropsSI('T', 'P', pressurePa, 'Q', 0, this.customRefString || refrigerant);
}
const subcooling = satTempK - tempK;
const result = {
type: 'success',
subcooling: Math.max(0, this._convertDeltaTempFromK(subcooling, tempUnit)), // can't have less than 0 degrees subcooling
saturationTemperature: this._convertTempFromK(satTempK, tempUnit),
refrigerant,
units: {
temperature: tempUnit,
pressure: pressureUnit
}
};
if(result.subcooling == Infinity && result.saturationTemperature == Infinity) {
return { type: 'error', message: 'Subcooling is infinity', note: 'If the pressures are in an expected range that this should work, please check your refrigerant type works in coolprop. "R507" for example is not supported, as it needs to be "R507a"'};
}
return result;
} catch (error) {
return { type: 'error', message: error.message };
}
}
async calculateSuperheat({ temperature, pressure, refrigerant = this.defaultRefrigerant, tempUnit = this.defaultTempUnit, pressureUnit = this.defaultPressureUnit }) {
try {
await this._ensureInit({ refrigerant, tempUnit, pressureUnit });
const tempK = this._convertTempToK(temperature, tempUnit);
const pressurePa = this._convertPressureToPa(pressure, pressureUnit);
//console.log(`In calculateSuperheat, pressurePa: ${pressurePa}, pressure: ${pressure}, pressureUnit: ${pressureUnit}, refrigerant: ${this.customRefString || refrigerant}`);
let satTempK;
if(this.customRef){
// Use vapor pressure for superheat
satTempK = this._interpolateSaturationTemperature(pressurePa, customRefs[refrigerant].saturation, 'vapor');
}else{
satTempK = coolprop.PropsSI('T', 'P', pressurePa, 'Q', 1, this.customRefString || refrigerant);
}
const superheat = tempK - satTempK;
//console.log(`superheat: ${superheat}, calculatedSuperheat: ${this._convertDeltaTempFromK(superheat, tempUnit)}, calculatedSatTempK: ${this._convertTempFromK(satTempK, tempUnit)}, tempK: ${tempK}, tempUnit: ${tempUnit}, pressurePa: ${pressurePa}, pressureUnit: ${pressureUnit}`);
const result = {
type: 'success',
superheat: Math.max(0, this._convertDeltaTempFromK(superheat, tempUnit)), // can't have less than 0 degrees superheat
saturationTemperature: this._convertTempFromK(satTempK, tempUnit),
refrigerant,
units: {
temperature: tempUnit,
pressure: pressureUnit
}
};
if(result.superheat == Infinity && result.saturationTemperature == Infinity) {
return { type: 'error', message: 'Superheat is infinity', note: 'If the pressures are in an expected range that this should work, please check your refrigerant type works in coolprop. "R507" for example is not supported, as it needs to be "R507a"'};
}
return result;
} catch (error) {
return { type: 'error', message: error.message };
}
}
async getProperties({ temperature, pressure, refrigerant = this.defaultRefrigerant, tempUnit = this.defaultTempUnit, pressureUnit = this.defaultPressureUnit }) {
try {
await this._ensureInit({ refrigerant, tempUnit, pressureUnit });
const tempK = this._convertTempToK(temperature, tempUnit);
const pressurePa = this._convertPressureToPa(pressure, pressureUnit);
if(this.customRef){
return { type: 'error', message: 'Custom refrigerants are not supported for getProperties' };
}
const props = {
temperature: this._convertTempFromK(tempK, tempUnit),
pressure: this._convertPressureFromPa(pressurePa, pressureUnit),
density: coolprop.PropsSI('D', 'T', tempK, 'P', pressurePa, this.customRefString || refrigerant),
enthalpy: coolprop.PropsSI('H', 'T', tempK, 'P', pressurePa, this.customRefString || refrigerant),
entropy: coolprop.PropsSI('S', 'T', tempK, 'P', pressurePa, this.customRefString || refrigerant),
quality: coolprop.PropsSI('Q', 'T', tempK, 'P', pressurePa, this.customRefString || refrigerant),
conductivity: coolprop.PropsSI('L', 'T', tempK, 'P', pressurePa, this.customRefString || refrigerant),
viscosity: coolprop.PropsSI('V', 'T', tempK, 'P', pressurePa, this.customRefString || refrigerant),
specificHeat: coolprop.PropsSI('C', 'T', tempK, 'P', pressurePa, this.customRefString || refrigerant)
};
return {
type: 'success',
properties: props,
refrigerant,
units: {
temperature: tempUnit,
pressure: pressureUnit,
density: 'kg/m³',
enthalpy: 'J/kg',
entropy: 'J/kg/K',
quality: 'dimensionless',
conductivity: 'W/m/K',
viscosity: 'Pa·s',
specificHeat: 'J/kg/K'
}
};
} catch (error) {
return { type: 'error', message: error.message };
}
}
// Direct access to CoolProp functions
async getPropsSI() {
if(!this.initialized) {
await coolprop.init();
}
return coolprop.PropsSI;
}
}
module.exports = new CoolPropWrapper();

View File

@@ -1,308 +0,0 @@
module.exports.R448a = {
saturation: [{
//values in kelvin, pascal
"K": 233.15,
"liquid": 135137.24,
"vapor": 101352.93
},
{
"K": 238.71,
"liquid": 173058.40,
"vapor": 131689.86
},
{
"K": 244.26,
"liquid": 218563.80,
"vapor": 168921.55
},
{
"K": 249.82,
"liquid": 273032.38,
"vapor": 214426.94
},
{
"K": 255.37,
"liquid": 337153.62,
"vapor": 268895.52
},
{
"K": 260.93,
"liquid": 412306.47,
"vapor": 333016.76
},
{
"K": 266.48,
"liquid": 499869.88,
"vapor": 408859.09
},
{
"K": 272.04,
"liquid": 599843.86,
"vapor": 496422.50
},
{
"K": 277.59,
"liquid": 714986.30,
"vapor": 598464.91
},
{
"K": 283.15,
"liquid": 845986.68,
"vapor": 714986.30
},
{
"K": 288.71,
"liquid": 990776.58,
"vapor": 845986.68
},
{
"K": 294.26,
"liquid": 1163145.51,
"vapor": 997671.34
},
{
"K": 299.82,
"liquid": 1349303.94,
"vapor": 1170040.26
},
{
"K": 305.37,
"liquid": 1556146.65,
"vapor": 1363093.46
},
{
"K": 310.93,
"liquid": 1783673.64,
"vapor": 1576830.93
},
{
"K": 316.48,
"liquid": 2038779.64,
"vapor": 1818147.42
},
{
"K": 322.04,
"liquid": 2314569.92,
"vapor": 2087042.94
},
{
"K": 327.59,
"liquid": 2617939.23,
"vapor": 2383517.49
},
{
"K": 333.15,
"liquid": 2955782.33,
"vapor": 2714465.83
},
{
"K": 338.71,
"liquid": 3321204.45,
"vapor": 3086782.71
}]
}
module.exports.R448A = module.exports.R448a;
module.exports.R449A = {
saturation: [
{
// values in kelvin, pascal
"K": 233.15,
"liquid": 134447.82,
"vapor": 101352.97
},
{
"K": 235.93,
"liquid": 152374.20,
"vapor": 115121.57
},
{
"K": 238.71,
"liquid": 171679.52,
"vapor": 131689.92
},
{
"K": 241.48,
"liquid": 193052.21,
"vapor": 148949.73
},
{
"K": 244.26,
"liquid": 216503.85,
"vapor": 168255.05
},
{
"K": 247.04,
"liquid": 242702.42,
"vapor": 189627.74
},
{
"K": 249.82,
"liquid": 270979.90,
"vapor": 213768.86
},
{
"K": 252.59,
"liquid": 301336.31,
"vapor": 240051.48
},
{
"K": 255.37,
"liquid": 334440.63,
"vapor": 267609.92
},
{
"K": 258.15,
"liquid": 370292.86,
"vapor": 298655.80
},
{
"K": 260.93,
"liquid": 408892.90,
"vapor": 331760.12
},
{
"K": 263.71,
"liquid": 450240.76,
"vapor": 367612.35
},
{
"K": 266.48,
"liquid": 495036.08,
"vapor": 406831.32
},
{
"K": 269.26,
"liquid": 542579.32,
"vapor": 448868.64
},
{
"K": 272.04,
"liquid": 594279.82,
"vapor": 493663.96
},
{
"K": 274.82,
"liquid": 649728.18,
"vapor": 542579.32
},
{
"K": 277.59,
"liquid": 708053.32,
"vapor": 594969.28
},
{
"K": 280.37,
"liquid": 770873.08,
"vapor": 650767.64
},
{
"K": 283.15,
"liquid": 839126.92,
"vapor": 710801.16
},
{
"K": 285.93,
"liquid": 912814.72,
"vapor": 774989.44
},
{
"K": 288.71,
"liquid": 983940.92,
"vapor": 845977.32
},
{
"K": 291.48,
"liquid": 1066606.52,
"vapor": 914889.32
},
{
"K": 294.26,
"liquid": 1151351.00,
"vapor": 990835.62
},
{
"K": 297.04,
"liquid": 1238843.30,
"vapor": 1073501.22
},
{
"K": 299.82,
"liquid": 1335552.20,
"vapor": 1165089.32
},
{
"K": 302.59,
"liquid": 1432261.10,
"vapor": 1256677.42
},
{
"K": 305.37,
"liquid": 1535864.72,
"vapor": 1357134.12
},
{
"K": 308.15,
"liquid": 1646363.00,
"vapor": 1457590.92
},
{
"K": 310.93,
"liquid": 1763756.02,
"vapor": 1568089.12
},
{
"K": 313.71,
"liquid": 1887043.62,
"vapor": 1678587.32
},
{
"K": 316.48,
"liquid": 2017225.92,
"vapor": 1802217.02
},
{
"K": 319.26,
"liquid": 2147408.22,
"vapor": 1934952.12
},
{
"K": 322.04,
"liquid": 2291329.82,
"vapor": 2072621.52
},
{
"K": 324.82,
"liquid": 2435251.42,
"vapor": 2217185.62
},
{
"K": 327.59,
"liquid": 2592912.32,
"vapor": 2368644.42
},
{
"K": 330.37,
"liquid": 2750573.22,
"vapor": 2526305.32
},
{
"K": 333.15,
"liquid": 2925424.52,
"vapor": 2690860.82
},
{
"K": 335.93,
"liquid": 3100275.92,
"vapor": 2871668.52
},
{
"K": 338.71,
"liquid": 3288922.02,
"vapor": 3059370.92
}
]}
module.exports.R449a = module.exports.R449A;

View File

@@ -1,94 +0,0 @@
const coolprop = require('../src/index.js');
describe('R448a Real Values', () => {
it('should calculate superheat correctly at -40°C saturation', async () => {
const result = await coolprop.calculateSuperheat({
temperature: -35, // 5K above saturation temp of -40°C
pressure: 0, // saturation pressure at -40°C (from chart)
refrigerant: 'R448a',
tempUnit: 'C',
pressureUnit: 'psig'
});
expect(result.type).toBe('success');
expect(Math.abs(result.superheat - 5)).toBeLessThan(0.2); // Should be ~5K superheat
});
it('should calculate superheat correctly at -20°C saturation', async () => {
const result = await coolprop.calculateSuperheat({
temperature: -15, // 5K above saturation temp of -20°C
pressure: 21.0, // saturation pressure at -20°C (from chart)
refrigerant: 'R448a',
tempUnit: 'C',
pressureUnit: 'psig'
});
//console.log(result);
expect(result.type).toBe('success');
expect(Math.abs(result.superheat - 5)).toBeLessThan(0.2); // Should be ~5K superheat
});
it('should calculate subcooling correctly at 30°C saturation', async () => {
const result = await coolprop.calculateSubcooling({
temperature: 25, // 5K below saturation temp of 30°C
pressure: 198.1, // saturation pressure at 30°C (from chart)
refrigerant: 'R448a',
tempUnit: 'C',
pressureUnit: 'psig'
});
expect(result.type).toBe('success');
expect(Math.abs(result.subcooling - 5)).toBeLessThan(0.2); // Should be ~5K subcooling
});
it('should calculate subcooling correctly at 40°C saturation', async () => {
const result = await coolprop.calculateSubcooling({
temperature: 35, // 5K below saturation temp of 40°C
pressure: 258.0, // saturation pressure at 40°C (from chart)
refrigerant: 'R448a',
tempUnit: 'C',
pressureUnit: 'psig'
});
expect(result.type).toBe('success');
expect(Math.abs(result.subcooling - 5)).toBeLessThan(0.2); // Should be ~5K subcooling
});
it('should calculate zero superheat at saturation point', async () => {
const result = await coolprop.calculateSuperheat({
temperature: 0, // Exact saturation temperature
pressure: 60.1, // Matching saturation pressure from chart
refrigerant: 'R448a',
tempUnit: 'C',
pressureUnit: 'psig'
});
expect(result.type).toBe('success');
expect(Math.abs(result.superheat)).toBeLessThan(0.2); // Should be ~0K superheat
});
it('should calculate zero subcooling at saturation point', async () => {
const result = await coolprop.calculateSubcooling({
temperature: 20, // Exact saturation temperature
pressure: 148.5, // Matching saturation pressure from chart
refrigerant: 'R448a',
tempUnit: 'C',
pressureUnit: 'psig'
});
expect(result.type).toBe('success');
expect(Math.abs(result.subcooling)).toBeLessThan(0.2); // Should be ~0K subcooling
});
it('It should also work with R448A (capital A)', async () => {
const result = await coolprop.calculateSubcooling({
temperature: 20, // Exact saturation temperature
pressure: 148.5, // Matching saturation pressure from chart
refrigerant: 'R448A',
tempUnit: 'C',
pressureUnit: 'psig'
});
expect(result.type).toBe('success');
expect(Math.abs(result.subcooling)).toBeLessThan(0.2); // Should be ~0K subcooling
});
});

View File

@@ -1,94 +0,0 @@
const coolprop = require('../src/index.js');
describe('R449a Real Values', () => {
it('should calculate superheat correctly at -40°C saturation', async () => {
const result = await coolprop.calculateSuperheat({
temperature: -35, // 5K above saturation temp of -40°C
pressure: 0, // saturation pressure at -40°C (from chart)
refrigerant: 'R449a',
tempUnit: 'C',
pressureUnit: 'psig'
});
expect(result.type).toBe('success');
expect(Math.abs(result.superheat - 5)).toBeLessThan(0.2); // Should be ~5K superheat
});
it('should calculate superheat correctly at -20°C saturation', async () => {
const result = await coolprop.calculateSuperheat({
temperature: -15, // 5K above saturation temp of -20°C
pressure: 20.96, // saturation pressure at -20°C (from chart)
refrigerant: 'R449a',
tempUnit: 'C',
pressureUnit: 'psig'
});
//console.log(result);
expect(result.type).toBe('success');
expect(Math.abs(result.superheat - 5)).toBeLessThan(0.2); // Should be ~5K superheat
});
it('should calculate subcooling correctly at 30°C saturation', async () => {
const result = await coolprop.calculateSubcooling({
temperature: 25, // 5K below saturation temp of 30°C
pressure: 195, // saturation pressure at 30°C (from chart)
refrigerant: 'R449a',
tempUnit: 'C',
pressureUnit: 'psig'
});
expect(result.type).toBe('success');
expect(Math.abs(result.subcooling - 5)).toBeLessThan(0.2); // Should be ~5K subcooling
});
it('should calculate subcooling correctly at 40°C saturation', async () => {
const result = await coolprop.calculateSubcooling({
temperature: 35, // 5K below saturation temp of 40°C
pressure: 254.2, // saturation pressure at 40°C (from chart)
refrigerant: 'R449a',
tempUnit: 'C',
pressureUnit: 'psig'
});
expect(result.type).toBe('success');
expect(Math.abs(result.subcooling - 5)).toBeLessThan(0.2); // Should be ~5K subcooling
});
it('should calculate zero superheat at saturation point', async () => {
const result = await coolprop.calculateSuperheat({
temperature: 0, // Exact saturation temperature
pressure: 74.05, // Matching saturation pressure from chart
refrigerant: 'R449a',
tempUnit: 'C',
pressureUnit: 'psig'
});
expect(result.type).toBe('success');
expect(Math.abs(result.superheat)).toBeLessThan(0.2); // Should be ~0K superheat
});
it('should calculate zero subcooling at saturation point', async () => {
const result = await coolprop.calculateSubcooling({
temperature: 20, // Exact saturation temperature
pressure: 146.0, // Matching saturation pressure from chart
refrigerant: 'R449a',
tempUnit: 'C',
pressureUnit: 'psig'
});
expect(result.type).toBe('success');
expect(Math.abs(result.subcooling)).toBeLessThan(0.2); // Should be ~0K subcooling
});
it('It should also work with R449A (capital A)', async () => {
const result = await coolprop.calculateSubcooling({
temperature: 20, // Exact saturation temperature
pressure: 146.0, // Matching saturation pressure from chart
refrigerant: 'R449A',
tempUnit: 'C',
pressureUnit: 'psig'
});
expect(result.type).toBe('success');
expect(Math.abs(result.subcooling)).toBeLessThan(0.2); // Should be ~0K subcooling
});
});

View File

@@ -1,97 +0,0 @@
const coolprop = require('../src/index.js');
describe('R507 Real Values', () => {
it('should calculate superheat correctly at -40°C saturation', async () => {
const result = await coolprop.calculateSuperheat({
temperature: -35, // 5K above saturation temp of -40°C
pressure: 5.4, // saturation pressure at -40°C (from chart)
refrigerant: 'R507a',
tempUnit: 'C',
pressureUnit: 'psig'
});
expect(result.type).toBe('success');
expect(Math.abs(result.superheat - 5)).toBeLessThan(0.1); // Should be ~5K superheat
});
it('should calculate superheat correctly at -20°C saturation', async () => {
const result = await coolprop.calculateSuperheat({
temperature: -15, // 5K above saturation temp of -20°C
pressure: 30.9, // saturation pressure at -20°C (from chart)
refrigerant: 'R507a',
tempUnit: 'C',
pressureUnit: 'psig'
});
expect(result.type).toBe('success');
expect(Math.abs(result.superheat - 5)).toBeLessThan(0.1); // Should be ~5K superheat
});
it('should calculate subcooling correctly at 30°C saturation', async () => {
const result = await coolprop.calculateSubcooling({
temperature: 25, // 5K below saturation temp of 30°C
pressure: 196.9, // saturation pressure at 30°C (from chart)
refrigerant: 'R507a',
tempUnit: 'C',
pressureUnit: 'psig'
});
expect(result.type).toBe('success');
expect(Math.abs(result.subcooling - 5)).toBeLessThan(0.1); // Should be ~5K subcooling
});
it('should calculate subcooling correctly at 40°C saturation', async () => {
const result = await coolprop.calculateSubcooling({
temperature: 35, // 5K below saturation temp of 40°C
pressure: 256.2, // saturation pressure at 40°C (from chart)
refrigerant: 'R507a',
tempUnit: 'C',
pressureUnit: 'psig'
});
expect(result.type).toBe('success');
expect(Math.abs(result.subcooling - 5)).toBeLessThan(0.1); // Should be ~5K subcooling
});
it('should calculate zero superheat at saturation point', async () => {
const result = await coolprop.calculateSuperheat({
temperature: 0, // Exact saturation temperature
pressure: 75.8, // Matching saturation pressure from chart
refrigerant: 'R507a',
tempUnit: 'C',
pressureUnit: 'psig'
});
expect(result.type).toBe('success');
expect(Math.abs(result.superheat)).toBeLessThan(0.1); // Should be ~0K superheat
});
it('should calculate zero subcooling at saturation point', async () => {
const result = await coolprop.calculateSubcooling({
temperature: 20, // Exact saturation temperature
pressure: 148, // Matching saturation pressure from chart
refrigerant: 'R507a',
tempUnit: 'C',
pressureUnit: 'psig'
});
expect(result.type).toBe('success');
expect(Math.abs(result.subcooling)).toBeLessThan(0.1); // Should be ~0K subcooling
});
it('should calculate subcooling correctly at 30°C saturation', async () => {
const result = await coolprop.calculateSubcooling({
temperature: 25, // 5K below saturation temp of 30°C
pressure: 196.9, // saturation pressure at 30°C (from chart)
refrigerant: 'R507',
tempUnit: 'C',
pressureUnit: 'psig'
});
expect(result.type).toBe('error');
expect(result.message).toBe('Subcooling is infinity');
expect(result.note).toBeDefined();
});
});

View File

@@ -1,55 +0,0 @@
const coolprop = require('../src/index.js');
describe('R744 (CO2) Real Values', () => {
it('should calculate superheat correctly at -40°C saturation', async () => {
const result = await coolprop.calculateSuperheat({
temperature: -35, // 5K above saturation temp of -40°C
pressure: 9.03, // saturation pressure at -40°C (from chart)
refrigerant: 'R744',
tempUnit: 'C',
pressureUnit: 'bar'
});
expect(result.type).toBe('success');
expect(Math.abs(result.superheat - 5)).toBeLessThan(0.1); // Should be ~5K superheat
});
it('should calculate subcooling correctly at 0°C saturation', async () => {
const result = await coolprop.calculateSubcooling({
temperature: -5, // 5K below saturation temp of 0°C
pressure: 33.84, // saturation pressure at 0°C (from chart)
refrigerant: 'R744',
tempUnit: 'C',
pressureUnit: 'bar'
});
expect(result.type).toBe('success');
expect(Math.abs(result.subcooling - 5)).toBeLessThan(0.1); // Should be ~5K subcooling
});
it('should calculate zero superheat at saturation point', async () => {
const result = await coolprop.calculateSuperheat({
temperature: -20, // Exact saturation temperature
pressure: 18.68, // Matching saturation pressure from chart
refrigerant: 'R744',
tempUnit: 'C',
pressureUnit: 'bar'
});
expect(result.type).toBe('success');
expect(Math.abs(result.superheat)).toBeLessThan(0.1); // Should be ~0K superheat
});
it('should calculate zero subcooling at saturation point', async () => {
const result = await coolprop.calculateSubcooling({
temperature: 10, // Exact saturation temperature
pressure: 44.01, // Matching saturation pressure from chart
refrigerant: 'R744',
tempUnit: 'C',
pressureUnit: 'bar'
});
expect(result.type).toBe('success');
expect(Math.abs(result.subcooling)).toBeLessThan(0.1); // Should be ~0K subcooling
});
});

View File

@@ -1,55 +0,0 @@
const coolprop = require('../src/index.js');
describe('R744 (CO2) Real Values', () => {
it('should calculate superheat correctly at -40°F saturation', async () => {
const result = await coolprop.calculateSuperheat({
temperature: -35, // 5°F above saturation temp of -40°F
pressure: 131, // saturation pressure at -40°F (from chart)
refrigerant: 'R744',
tempUnit: 'F', // Changed to F
pressureUnit: 'psig'
});
expect(result.type).toBe('success');
expect(Math.abs(result.superheat - 5)).toBeLessThan(0.1); // Should be ~5°F superheat
});
it('should calculate subcooling correctly at 32°F saturation', async () => {
const result = await coolprop.calculateSubcooling({
temperature: 27, // 5°F below saturation temp of 32°F
pressure: 490.8, // saturation pressure at 32°F (from chart)
refrigerant: 'R744',
tempUnit: 'F', // Changed to F
pressureUnit: 'psig'
});
expect(result.type).toBe('success');
expect(Math.abs(result.subcooling - 5)).toBeLessThan(0.1); // Should be ~5°F subcooling
});
it('should calculate zero superheat at saturation point', async () => {
const result = await coolprop.calculateSuperheat({
temperature: 32, // Exact saturation temperature
pressure: 490.8, // Matching saturation pressure from chart
refrigerant: 'R744',
tempUnit: 'F', // Changed to F
pressureUnit: 'psig'
});
expect(result.type).toBe('success');
expect(Math.abs(result.superheat)).toBeLessThan(0.1); // Should be ~0°F superheat
});
it('should calculate zero subcooling at saturation point', async () => {
const result = await coolprop.calculateSubcooling({
temperature: 32, // Exact saturation temperature
pressure: 490.8, // Matching saturation pressure from chart
refrigerant: 'R744',
tempUnit: 'F', // Changed to F
pressureUnit: 'psig'
});
expect(result.type).toBe('success');
expect(Math.abs(result.subcooling)).toBeLessThan(0.1); // Should be ~0°F subcooling
});
});

View File

@@ -1,296 +0,0 @@
const coolprop = require('../src/index.js');
describe('CoolProp Wrapper', () => {
describe('Initialization', () => {
it('should fail without refrigerant', async () => {
const result = await coolprop.init({});
expect(result.type).toBe('error');
expect(result.message).toContain('Refrigerant must be specified');
});
it('should fail with invalid temperature unit', async () => {
const result = await coolprop.init({ refrigerant: 'R404A', tempUnit: 'X' });
expect(result.type).toBe('error');
expect(result.message).toContain('Invalid temperature unit');
});
it('should fail with invalid pressure unit', async () => {
const result = await coolprop.init({ refrigerant: 'R404A', pressureUnit: 'X' });
expect(result.type).toBe('error');
expect(result.message).toContain('Invalid pressure unit');
});
it('should succeed with valid config', async () => {
const result = await coolprop.init({
refrigerant: 'R404A',
tempUnit: 'C',
pressureUnit: 'bar'
});
console.log(result);
expect(result.type).toBe('success');
});
});
describe('Auto-initialization', () => {
it('should work without explicit init', async () => {
const result = await coolprop.calculateSuperheat({
temperature: 25,
pressure: 10,
refrigerant: 'R404A',
tempUnit: 'C',
pressureUnit: 'bar'
});
expect(result.type).toBe('success');
expect(result.superheat).toBeDefined();
});
});
describe('Unit Conversions', () => {
it('should correctly convert temperature units', async () => {
const resultC = await coolprop.getSaturationTemperature({
pressure: 10,
refrigerant: 'R404A',
pressureUnit: 'bar',
tempUnit: 'C'
});
const resultF = await coolprop.getSaturationTemperature({
pressure: 10,
refrigerant: 'R404A',
pressureUnit: 'bar',
tempUnit: 'F'
});
const resultK = await coolprop.getSaturationTemperature({
pressure: 10,
refrigerant: 'R404A',
pressureUnit: 'bar',
tempUnit: 'K'
});
expect(Math.abs((resultC.temperature * 9/5 + 32) - resultF.temperature)).toBeLessThan(0.01);
expect(Math.abs((resultC.temperature + 273.15) - resultK.temperature)).toBeLessThan(0.01);
});
it('should correctly convert pressure units', async () => {
const resultBar = await coolprop.getSaturationPressure({
temperature: 25,
refrigerant: 'R404A',
tempUnit: 'C',
pressureUnit: 'bar'
});
const resultPsi = await coolprop.getSaturationPressure({
temperature: 25,
refrigerant: 'R404A',
tempUnit: 'C',
pressureUnit: 'psi'
});
expect(Math.abs((resultBar.pressure * 14.5038) - resultPsi.pressure)).toBeLessThan(0.1);
});
});
describe('Refrigerant Calculations', () => {
const refrigerants = ['R404A', 'R134a', 'R507A', 'R744'];
refrigerants.forEach(refrigerant => {
describe(refrigerant, () => {
it('should calculate superheat', async () => {
const result = await coolprop.calculateSuperheat({
temperature: 25,
pressure: 10,
refrigerant,
tempUnit: 'C',
pressureUnit: 'bar'
});
expect(result.type).toBe('success');
expect(result.superheat).toBeDefined();
expect(result.refrigerant).toBe(refrigerant);
expect(result.units).toEqual(expect.objectContaining({
temperature: 'C',
pressure: 'bar'
}));
});
it('should calculate subcooling', async () => {
const result = await coolprop.calculateSubcooling({
temperature: 20,
pressure: 20,
refrigerant,
tempUnit: 'C',
pressureUnit: 'bar'
});
expect(result.type).toBe('success');
expect(result.subcooling).toBeDefined();
expect(result.refrigerant).toBe(refrigerant);
});
it('should get all properties', async () => {
const result = await coolprop.getProperties({
temperature: 25,
pressure: 10,
refrigerant,
tempUnit: 'C',
pressureUnit: 'bar'
});
expect(result.type).toBe('success');
expect(result.properties).toBeDefined();
expect(result.refrigerant).toBe(refrigerant);
// Check all required properties exist
const requiredProps = [
'temperature', 'pressure', 'density', 'enthalpy',
'entropy', 'quality', 'conductivity', 'viscosity', 'specificHeat'
];
requiredProps.forEach(prop => {
expect(result.properties[prop]).toBeDefined();
expect(typeof result.properties[prop]).toBe('number');
});
});
});
});
});
describe('Default Override Behavior', () => {
beforeAll(async () => {
await coolprop.init({
refrigerant: 'R404A',
tempUnit: 'C',
pressureUnit: 'bar'
});
});
it('should use defaults when no overrides provided', async () => {
const result = await coolprop.calculateSuperheat({
temperature: 25,
pressure: 10
});
expect(result.refrigerant).toBe('R404A');
expect(result.units.temperature).toBe('C');
expect(result.units.pressure).toBe('bar');
});
it('should allow refrigerant override', async () => {
const result = await coolprop.calculateSuperheat({
temperature: 25,
pressure: 10,
refrigerant: 'R134a'
});
expect(result.refrigerant).toBe('R134a');
});
it('should allow unit overrides', async () => {
const result = await coolprop.calculateSuperheat({
temperature: 77,
pressure: 145,
tempUnit: 'F',
pressureUnit: 'psi'
});
expect(result.units.temperature).toBe('F');
expect(result.units.pressure).toBe('psi');
});
});
describe('Default Settings Management', () => {
it('should allow updating defaults after initialization', async () => {
// Initial setup
await coolprop.init({
refrigerant: 'R404A',
tempUnit: 'C',
pressureUnit: 'bar'
});
// Update defaults
const updateResult = await coolprop.init({
refrigerant: 'R134a',
tempUnit: 'F',
pressureUnit: 'psi'
});
expect(updateResult.type).toBe('success');
expect(updateResult.message).toBe('Default settings updated');
// Verify new defaults are used
const result = await coolprop.calculateSuperheat({
temperature: 77,
pressure: 145
});
expect(result.refrigerant).toBe('R134a');
expect(result.units.temperature).toBe('F');
expect(result.units.pressure).toBe('psi');
});
it('should update the coolprop instance if refrigerant is changed', async () => {
// Set initial defaults
await coolprop.init({
refrigerant: 'R404A',
tempUnit: 'C',
pressureUnit: 'bar'
});
const config = await coolprop.getConfig();
// First call with overrides
const result1 = await coolprop.calculateSuperheat({
temperature: 25,
pressure: 10,
refrigerant: 'R507A',
tempUnit: 'C',
pressureUnit: 'bar'
});
// Second call using defaults
const result2 = await coolprop.calculateSuperheat({
temperature: 25,
pressure: 10
});
const config2 = await coolprop.getConfig();
expect(config.refrigerant).toBe('R404A');
expect(config2.refrigerant).toBe('R507A');
expect(result1.refrigerant).toBe('R507A');
expect(result2.refrigerant).toBe('R507A');
});
it('should allow partial updates of defaults', async () => {
// Initial setup
await coolprop.init({
refrigerant: 'R404A',
tempUnit: 'C',
pressureUnit: 'bar'
});
// Update only temperature unit
await coolprop.init({
tempUnit: 'F'
});
const result = await coolprop.calculateSuperheat({
temperature: 77,
pressure: 10
});
expect(result.refrigerant).toBe('R404A'); // unchanged
expect(result.units.temperature).toBe('F'); // updated
expect(result.units.pressure).toBe('bar'); // unchanged
});
it('should validate units when updating defaults', async () => {
await coolprop.init({
refrigerant: 'R404A',
tempUnit: 'C',
pressureUnit: 'bar'
});
const result = await coolprop.init({
tempUnit: 'X' // invalid unit
});
expect(result.type).toBe('error');
expect(result.message).toContain('Invalid temperature unit');
});
});
});

View File

@@ -1,58 +0,0 @@
const coolprop = require('../src/index.js');
describe('Pressure Conversion Chain Tests', () => {
test('bar -> pa -> bara -> pa -> bar conversion chain', () => {
const startValue = 2; // 2 bar gauge
const toPa = coolprop._convertPressureToPa(startValue, 'bar');
// console.log('bar to Pa:', toPa);
const toBara = coolprop._convertPressureFromPa(toPa, 'bara');
// console.log('Pa to bara:', toBara);
const backToPa = coolprop._convertPressureToPa(toBara, 'bara');
// console.log('bara to Pa:', backToPa);
const backToBar = coolprop._convertPressureFromPa(backToPa, 'bar');
// console.log('Pa to bar:', backToBar);
expect(Math.round(backToBar * 1000) / 1000).toBe(startValue);
});
test('psi -> pa -> psia -> pa -> psi conversion chain', () => {
const startValue = 30; // 30 psi gauge
const toPa = coolprop._convertPressureToPa(startValue, 'psi');
// console.log('psi to Pa:', toPa);
const toPsia = coolprop._convertPressureFromPa(toPa, 'psia');
// console.log('Pa to psia:', toPsia);
const backToPa = coolprop._convertPressureToPa(toPsia, 'psia');
// console.log('psia to Pa:', backToPa);
const backToPsi = coolprop._convertPressureFromPa(backToPa, 'psi');
// console.log('Pa to psi:', backToPsi);
expect(Math.round(backToPsi * 1000) / 1000).toBe(startValue);
});
test('kpa -> pa -> kpaa -> pa -> kpa conversion chain', () => {
const startValue = 200; // 200 kPa gauge
const toPa = coolprop._convertPressureToPa(startValue, 'kpa');
// console.log('kpa to Pa:', toPa);
const toKpaa = coolprop._convertPressureFromPa(toPa, 'kpaa');
// console.log('Pa to kpaa:', toKpaa);
const backToPa = coolprop._convertPressureToPa(toKpaa, 'kpaa');
// console.log('kpaa to Pa:', backToPa);
const backToKpa = coolprop._convertPressureFromPa(backToPa, 'kpa');
// console.log('Pa to kpa:', backToKpa);
expect(Math.round(backToKpa * 1000) / 1000).toBe(startValue);
});
});

View File

@@ -1,50 +0,0 @@
const coolProp = require('../src/index.js');
describe('PropsSI Direct Access', () => {
let PropsSI;
beforeAll(async () => {
// Get the PropsSI function
PropsSI = await coolProp.getPropsSI();
});
test('should initialize and return PropsSI function', async () => {
expect(typeof PropsSI).toBe('function');
});
test('should calculate saturation temperature of R134a at 1 bar', () => {
const pressure = 100000; // 1 bar in Pa
const temp = PropsSI('T', 'P', pressure, 'Q', 0, 'R134a');
expect(temp).toBeCloseTo(246.79, 1); // ~246.79 K at 1 bar
});
test('should calculate density of R134a at specific conditions', () => {
const temp = 300; // 300 K
const pressure = 100000; // 1 bar in Pa
const density = PropsSI('D', 'T', temp, 'P', pressure, 'R134a');
expect(density).toBeGreaterThan(0)
expect(density).toBeLessThan(Infinity);
});
test('should throw error for invalid refrigerant', () => {
const temp = 300;
const pressure = 100000;
expect(() => {
let result = PropsSI('D', 'T', temp, 'P', pressure, 'INVALID_REFRIGERANT');
if(result == Infinity) {
throw new Error('Infinity due to invalid refrigerant');
}
}).toThrow();
});
test('should throw error for invalid input parameter', () => {
const temp = 300;
const pressure = 100000;
expect(() => {
let result = PropsSI('INVALID_PARAM', 'T', temp, 'P', pressure, 'R134a');
if(result == Infinity) {
throw new Error('Infinity due to invalid input parameter');
}
}).toThrow();
});
});

View File

@@ -1,128 +0,0 @@
const coolprop = require('../src/index.js');
describe('Temperature Conversion Tests', () => {
describe('Regular Temperature Conversions', () => {
const testCases = [
{
startUnit: 'C',
startValue: 25,
expectedK: 298.15,
conversions: {
F: 77,
K: 298.15,
C: 25
}
},
{
startUnit: 'F',
startValue: 77,
expectedK: 298.15,
conversions: {
F: 77,
K: 298.15,
C: 25
}
},
{
startUnit: 'K',
startValue: 298.15,
expectedK: 298.15,
conversions: {
F: 77,
K: 298.15,
C: 25
}
}
];
testCases.forEach(({ startUnit, startValue, expectedK, conversions }) => {
test(`${startValue}${startUnit} conversion chain`, () => {
// First convert to Kelvin
const toK = coolprop._convertTempToK(startValue, startUnit);
expect(Math.round(toK * 100) / 100).toBe(expectedK);
// Then convert from Kelvin to each unit
Object.entries(conversions).forEach(([unit, expected]) => {
const converted = coolprop._convertTempFromK(toK, unit);
expect(Math.round(converted * 100) / 100).toBe(expected);
});
});
});
});
describe('Delta Temperature Conversions', () => {
const testCases = [
{
startValue: 10, // 10K temperature difference
expected: {
K: 10,
C: 10,
F: 18 // 10K = 18°F difference
}
}
];
testCases.forEach(({ startValue, expected }) => {
test(`${startValue}K delta conversion to all units`, () => {
Object.entries(expected).forEach(([unit, expectedValue]) => {
const converted = coolprop._convertDeltaTempFromK(startValue, unit);
expect(Math.round(converted * 100) / 100).toBe(expectedValue);
});
});
});
});
describe('Common Temperature Points', () => {
const commonPoints = [
{
description: 'Water freezing point',
C: 0,
F: 32,
K: 273.15
},
{
description: 'Water boiling point',
C: 100,
F: 212,
K: 373.15
},
{
description: 'Room temperature',
C: 20,
F: 68,
K: 293.15
},
{
description: 'Typical refrigeration evaporator',
C: 5,
F: 41,
K: 278.15
},
{
description: 'Typical refrigeration condenser',
C: 35,
F: 95,
K: 308.15
}
];
commonPoints.forEach(point => {
test(`${point.description} conversions`, () => {
// Test conversion to Kelvin from each unit
const fromC = coolprop._convertTempToK(point.C, 'C');
const fromF = coolprop._convertTempToK(point.F, 'F');
expect(Math.round(fromC * 100) / 100).toBe(point.K);
expect(Math.round(fromF * 100) / 100).toBe(point.K);
// Test conversion from Kelvin to each unit
const toC = coolprop._convertTempFromK(point.K, 'C');
const toF = coolprop._convertTempFromK(point.K, 'F');
expect(Math.round(toC * 100) / 100).toBe(point.C);
expect(Math.round(toF * 100) / 100).toBe(point.F);
});
});
});
});

View File

@@ -5,14 +5,18 @@ class ChildRegistrationUtils {
this.registeredChildren = new Map(); this.registeredChildren = new Map();
} }
async registerChild(child, positionVsParent, distance) { async registerChild(child, positionVsParent) {
const { softwareType } = child.config.functionality; const { softwareType } = child.config.functionality;
const { name, id } = child.config.general; const { name, id } = child.config.general;
this.logger.debug(`Registering child: ${name} (${id}) as ${softwareType} at ${positionVsParent}`); this.logger.debug(`Registering child: ${name} (${id}) as ${softwareType} at ${positionVsParent}`);
// Enhanced child setup // Enhanced child setup - multiple parents
child.parent = this.mainClass; if (Array.isArray(child.parent)) {
child.parent.push(this.mainClass);
} else {
child.parent = [this.mainClass];
}
child.positionVsParent = positionVsParent; child.positionVsParent = positionVsParent;
// Enhanced measurement container with rich context // Enhanced measurement container with rich context

View File

@@ -2,12 +2,11 @@
const convertModule = require('../convert/index'); const convertModule = require('../convert/index');
class Measurement { class Measurement {
constructor(type, variant, position, windowSize, distance = null) { constructor(type, variant, position, windowSize) {
this.type = type; // e.g. 'pressure', 'flow', etc. this.type = type; // e.g. 'pressure', 'flow', etc.
this.variant = variant; // e.g. 'predicted' or 'measured', etc.. this.variant = variant; // e.g. 'predicted' or 'measured', etc..
this.position = position; // Downstream or upstream of parent object this.position = position; // Downstream or upstream of parent object
this.windowSize = windowSize; // Rolling window size this.windowSize = windowSize; // Rolling window size
this.distance = distance; // Distance from parent, if applicable
// Place all data inside an array // Place all data inside an array
this.values = []; // Array to store all values this.values = []; // Array to store all values
@@ -37,12 +36,13 @@ class Measurement {
return this; return this;
} }
setDistance(distance) {
this.distance = distance;
return this;
}
setValue(value, timestamp = Date.now()) { setValue(value, timestamp = Date.now()) {
/*
if (value === undefined || value === null) {
value = null ;
//throw new Error('Value cannot be null or undefined');
}
*/
//shift the oldest value //shift the oldest value
if(this.values.length >= this.windowSize){ if(this.values.length >= this.windowSize){
@@ -68,23 +68,6 @@ class Measurement {
return this.values[this.values.length - 1]; return this.values[this.values.length - 1];
} }
getLaggedValue(lag){
if(this.values.length <= lag) return null;
return this.values[this.values.length - lag];
}
getLaggedSample(lag){
if (lag < 0) throw new Error('lag must be >= 0');
const index = this.values.length - 1 - lag;
if (index < 0) return null;
return {
value: this.values[index],
timestamp: this.timestamps[index],
unit: this.unit,
};
}
getAverage() { getAverage() {
if (this.values.length === 0) return null; if (this.values.length === 0) return null;
const sum = this.values.reduce((acc, val) => acc + val, 0); const sum = this.values.reduce((acc, val) => acc + val, 0);
@@ -185,8 +168,7 @@ class Measurement {
this.type, this.type,
this.variant, this.variant,
this.position, this.position,
this.windowSize, this.windowSize
this.distance
); );
// Copy values and timestamps // Copy values and timestamps

View File

@@ -5,7 +5,6 @@ class MeasurementBuilder {
this.type = null; this.type = null;
this.variant = null; this.variant = null;
this.position = null; this.position = null;
this.distance = null;
this.windowSize = 10; // Default window size this.windowSize = 10; // Default window size
} }
@@ -33,11 +32,6 @@ class MeasurementBuilder {
return this; return this;
} }
setDistance(distance) {
this.distance = distance;
return this;
}
build() { build() {
// Validate required fields // Validate required fields
if (!this.type) { if (!this.type) {
@@ -49,14 +43,12 @@ class MeasurementBuilder {
if (!this.position) { if (!this.position) {
throw new Error('Measurement position is required'); throw new Error('Measurement position is required');
} }
// distance is not a requirement as it can be derived from position
return new Measurement( return new Measurement(
this.type, this.type,
this.variant, this.variant,
this.position, this.position,
this.windowSize, this.windowSize
this.distance
); );

View File

@@ -3,7 +3,7 @@ const EventEmitter = require('events');
const convertModule = require('../convert/index'); const convertModule = require('../convert/index');
class MeasurementContainer { class MeasurementContainer {
constructor(options = {},logger) { constructor(options = {}) {
this.emitter = new EventEmitter(); this.emitter = new EventEmitter();
this.measurements = {}; this.measurements = {};
this.windowSize = options.windowSize || 10; // Default window size this.windowSize = options.windowSize || 10; // Default window size
@@ -12,7 +12,6 @@ class MeasurementContainer {
this._currentType = null; this._currentType = null;
this._currentVariant = null; this._currentVariant = null;
this._currentPosition = null; this._currentPosition = null;
this._currentDistance = null;
this._unit = null; this._unit = null;
// Default units for each measurement type // Default units for each measurement type
@@ -94,19 +93,12 @@ class MeasurementContainer {
throw new Error('Variant must be specified before position'); throw new Error('Variant must be specified before position');
} }
// Turn string positions into numeric values
this._currentPosition = positionValue.toString().toLowerCase();; if (typeof positionValue == "string") {
positionValue = this._convertPositionStr2Num(positionValue);
return this;
} }
distance(distance) { this._currentPosition = positionValue;
// If distance is not provided, derive from positionVsParent
if(distance === null) {
distance = this._convertPositionStr2Num(this._currentPosition);
}
this._currentDistance = distance;
return this; return this;
} }
@@ -153,13 +145,12 @@ class MeasurementContainer {
sourceUnit: sourceUnit, sourceUnit: sourceUnit,
timestamp, timestamp,
position: this._currentPosition, position: this._currentPosition,
distance: this._currentDistance,
variant: this._currentVariant, variant: this._currentVariant,
type: this._currentType, type: this._currentType,
// NEW: Enhanced context // NEW: Enhanced context
childId: this.childId, childId: this.childId,
childName: this.childName, childName: this.childName,
parentRef: this.parentRef, parentRef: this.parentRef
}; };
// Emit the exact event your parent expects // Emit the exact event your parent expects
@@ -169,50 +160,6 @@ class MeasurementContainer {
return this; return this;
} }
/**
* Check whether a measurement series exists.
*
* You can rely on the current chain (type/variant/position already set via
* type().variant().position()), or pass them explicitly via the options.
*
* @param {object} options
* @param {string} [options.type] Override the current type
* @param {string} [options.variant] Override the current variant
* @param {string} [options.position] Override the current position
* @param {boolean} [options.requireValues=false]
* When true, the series must contain at least one stored value.
*
* @returns {boolean}
*/
exists({ type, variant, position, requireValues = false } = {}) {
const typeKey = type ?? this._currentType;
if (!typeKey) return false;
const variantKey = variant ?? this._currentVariant;
if (!variantKey) return false;
const positionKey = position ?? this._currentPosition;
const typeBucket = this.measurements[typeKey];
if (!typeBucket) return false;
const variantBucket = typeBucket[variantKey];
if (!variantBucket) return false;
if (!positionKey) {
// No specific position requested just check the variant bucket.
return requireValues
? Object.values(variantBucket).some(m => m?.values?.length > 0)
: Object.keys(variantBucket).length > 0;
}
const measurement = variantBucket[positionKey];
if (!measurement) return false;
return requireValues ? measurement.values?.length > 0 : true;
}
unit(unitName) { unit(unitName) {
if (!this._ensureChainIsValid()) return this; if (!this._ensureChainIsValid()) return this;
@@ -292,100 +239,45 @@ class MeasurementContainer {
return measurement ? measurement.getAllValues() : null; return measurement ? measurement.getAllValues() : null;
} }
getLaggedValue(lag = 1,requestedUnit = null ){
const measurement = this.get();
if (!measurement) return null;
let sample = measurement.getLaggedSample(lag);
if (sample === null) return null;
const value = sample.value;
// Return as-is if no unit conversion requested
if (!requestedUnit) {
return value;
}
// Convert if needed
if (measurement.unit && requestedUnit !== measurement.unit) {
try {
const convertedValue = convertModule(value).from(measurement.unit).to(requestedUnit);
//replace old value in sample and return obj
sample.value = convertedValue ;
sample.unit = requestedUnit;
return sample;
} catch (error) {
if (this.logger) {
this.logger.error(`Unit conversion failed: ${error.message}`);
}
return sample; // Return original value if conversion fails
}
}
return value;
}
getLaggedSample(lag = 1,requestedUnit = null ){
const measurement = this.get();
if (!measurement) return null;
let sample = measurement.getLaggedSample(lag);
if (sample === null) return null;
// Return as-is if no unit conversion requested
if (!requestedUnit) {
return sample;
}
// Convert if needed
if (measurement.unit && requestedUnit !== measurement.unit) {
try {
const convertedValue = convertModule(value).from(measurement.unit).to(requestedUnit);
//replace old value in sample and return obj
sample.value = convertedValue ;
sample.unit = requestedUnit;
return sample;
} catch (error) {
if (this.logger) {
this.logger.error(`Unit conversion failed: ${error.message}`);
}
return sample; // Return original value if conversion fails
}
}
return sample;
}
// Difference calculations between positions // Difference calculations between positions
difference({ from = "downstream", to = "upstream", unit: requestedUnit } = {}) { difference(requestedUnit = null) {
if (!this._currentType || !this._currentVariant) { if (!this._currentType || !this._currentVariant) {
throw new Error("Type and variant must be specified for difference calculation"); throw new Error('Type and variant must be specified for difference calculation');
} }
const get = pos => const savedPosition = this._currentPosition;
this.measurements?.[this._currentType]?.[this._currentVariant]?.[pos] || null;
const a = get(from); // Get upstream and downstream measurements
const b = get(to); const positions = this.getPositions();
if (!a || !b || a.values.length === 0 || b.values.length === 0) {
this._currentPosition = Math.min(...positions);
const upstream = this.get();
this._currentPosition = Math.max(...positions);
const downstream = this.get();
this._currentPosition = savedPosition;
if (!upstream || !downstream || upstream.values.length === 0 || downstream.values.length === 0) {
return null; return null;
} }
const targetUnit = requestedUnit || a.unit || b.unit; // Get target unit for conversion
const aVal = this._convertValueToUnit(a.getCurrentValue(), a.unit, targetUnit); const targetUnit = requestedUnit || upstream.unit || downstream.unit;
const bVal = this._convertValueToUnit(b.getCurrentValue(), b.unit, targetUnit);
const aAvg = this._convertValueToUnit(a.getAverage(), a.unit, targetUnit); // Get values in the same unit
const bAvg = this._convertValueToUnit(b.getAverage(), b.unit, targetUnit); const upstreamValue = this._convertValueToUnit(upstream.getCurrentValue(), upstream.unit, targetUnit);
const downstreamValue = this._convertValueToUnit(downstream.getCurrentValue(), downstream.unit, targetUnit);
const upstreamAvg = this._convertValueToUnit(upstream.getAverage(), upstream.unit, targetUnit);
const downstreamAvg = this._convertValueToUnit(downstream.getAverage(), downstream.unit, targetUnit);
return { return {
value: aVal - bVal, value: downstreamValue - upstreamValue,
avgDiff: aAvg - bAvg, avgDiff: downstreamAvg - upstreamAvg,
unit: targetUnit, unit: targetUnit
from,
to,
}; };
} }
@@ -417,7 +309,6 @@ difference({ from = "downstream", to = "upstream", unit: requestedUnit } = {}) {
.setVariant(this._currentVariant) .setVariant(this._currentVariant)
.setPosition(this._currentPosition) .setPosition(this._currentPosition)
.setWindowSize(this.windowSize) .setWindowSize(this.windowSize)
.setDistance(this._currentDistance)
.build(); .build();
} }
@@ -437,7 +328,7 @@ difference({ from = "downstream", to = "upstream", unit: requestedUnit } = {}) {
Object.keys(this.measurements[this._currentType]) : []; Object.keys(this.measurements[this._currentType]) : [];
} }
getPositions() { getPositions(asNumber = false) {
if (!this._currentType || !this._currentVariant) { if (!this._currentType || !this._currentVariant) {
throw new Error('Type and variant must be specified before listing positions'); throw new Error('Type and variant must be specified before listing positions');
} }
@@ -447,9 +338,13 @@ difference({ from = "downstream", to = "upstream", unit: requestedUnit } = {}) {
return []; return [];
} }
if (asNumber) {
return Object.keys(this.measurements[this._currentType][this._currentVariant]); return Object.keys(this.measurements[this._currentType][this._currentVariant]);
} }
return Object.keys(this.measurements[this._currentType][this._currentVariant]).map(this._convertPositionNum2Str);
}
clear() { clear() {
this.measurements = {}; this.measurements = {};
this._currentType = null; this._currentType = null;
@@ -540,15 +435,18 @@ difference({ from = "downstream", to = "upstream", unit: requestedUnit } = {}) {
} }
_convertPositionNum2Str(positionValue) { _convertPositionNum2Str(positionValue) {
switch (positionValue) { if (positionValue === 0) {
case 0:
return "atEquipment"; return "atEquipment";
case (positionValue < 0): }
if (positionValue < 0) {
return "upstream"; return "upstream";
case (positionValue > 0): }
if (positionValue > 0) {
return "downstream"; return "downstream";
default: }
console.log(`Invalid position provided: ${positionValue}`);
if (this.logger) {
this.logger.error(`Invalid position provided: ${positionValue}`);
} }
} }

View File

@@ -7,374 +7,249 @@ console.log('retrieving, and converting measurement data with automatic unit han
// ==================================== // ====================================
// BASIC SETUP EXAMPLES // BASIC SETUP EXAMPLES
// ==================================== // ====================================
console.log('--- Example 1: Basic Setup & Distance ---'); console.log('--- Example 1: Basic Setup & Event Subscription ---');
// Create a basic container // Create a basic container
const basicContainer = new MeasurementContainer({ windowSize: 20 }); const basicContainer = new MeasurementContainer({ windowSize: 20 });
// Subscribe to events to monitor changes // Subscribe to flow events to monitor changes
basicContainer.emitter.on('flow.predicted.upstream', (data) => { basicContainer.emitter.on('flow.predicted.upstream', (data) => {
console.log(`📡 Event: Flow predicted upstream = ${data.value} ${data.unit || ''} (distance=${data.distance ?? 'n/a'}m)`); console.log(`📡 Event: Flow predicted upstream update: ${data.value} at ${new Date(data.timestamp).toLocaleTimeString()}`);
}); });
// Subscribe to all measured flow events using wildcard //show all flow values from variant measured
basicContainer.emitter.on('flow.measured.*', (data) => { basicContainer.emitter.on('flow.measured.*', (data) => {
console.log(`📡 Event: Flow measured ${data.position} = ${data.value} ${data.unit || ''} (distance=${data.distance ?? 'n/a'}m)`); console.log(`📡 Event---------- I DID IT: Flow measured ${data.position} update: ${data.value}`)
}); });
// Basic value setting with distance // Basic value setting with chaining
console.log('\nSetting pressure values with distances:'); console.log('Setting basic pressure values...');
basicContainer basicContainer.type('pressure').variant('measured').position('upstream').value(100).unit('psi');
.type('pressure') basicContainer.type('pressure').variant('measured').position('downstream').value(95).unit('psi');
.variant('measured') basicContainer.type('pressure').variant('measured').position('downstream').value(80); // Additional value
.position('upstream')
.distance(1.5)
.value(100)
.unit('psi');
basicContainer
.type('pressure')
.variant('measured')
.position('downstream')
.distance(5.2)
.value(95)
.unit('psi');
// Distance persists - no need to set it again for same position
basicContainer
.type('pressure')
.variant('measured')
.position('downstream')
.value(90); // distance 5.2 is automatically reused
console.log('✅ Basic setup complete\n'); console.log('✅ Basic setup complete\n');
// Retrieve and display the distance
const upstreamPressure = basicContainer
.type('pressure')
.variant('measured')
.position('upstream')
.get();
console.log(`Retrieved upstream pressure: ${upstreamPressure.getCurrentValue()} ${upstreamPressure.unit}`);
console.log(`Distance from parent: ${upstreamPressure.distance ?? 'not set'} m\n`);
// ==================================== // ====================================
// AUTO-CONVERSION SETUP // AUTO-CONVERSION SETUP EXAMPLES
// ==================================== // ====================================
console.log('--- Example 2: Auto-Conversion Setup ---'); console.log('--- Example 2: Auto-Conversion Setup ---');
console.log('Setting up a container with automatic unit conversion...\n');
// Create container with auto-conversion enabled
const autoContainer = new MeasurementContainer({ const autoContainer = new MeasurementContainer({
autoConvert: true, autoConvert: true,
windowSize: 50, windowSize: 50,
defaultUnits: { defaultUnits: {
pressure: 'bar', pressure: 'bar', // Default pressure unit
flow: 'l/min', flow: 'l/min', // Default flow unit
power: 'kW', power: 'kW', // Default power unit
temperature: 'C' temperature: 'C' // Default temperature unit
}, },
preferredUnits: { preferredUnits: {
pressure: 'psi' pressure: 'psi' // Override: store pressure in PSI instead of bar
} }
}); });
// Values automatically convert to preferred units // Values are automatically converted to preferred units
console.log('Adding pressure with auto-conversion:'); console.log('Adding pressure data with auto-conversion:');
autoContainer autoContainer.type('pressure').variant('measured').position('upstream')
.type('pressure')
.variant('measured')
.position('upstream')
.distance(0.5)
.value(1.5, Date.now(), 'bar'); // Input: 1.5 bar → Auto-stored as ~21.76 psi .value(1.5, Date.now(), 'bar'); // Input: 1.5 bar → Auto-stored as ~21.76 psi
autoContainer.type('pressure').variant('measured').position('downstream')
.value(20, Date.now(), 'psi'); // Input: 20 psi → Stored as 20 psi (already in preferred unit)
const converted = autoContainer // Check what was actually stored
.type('pressure') const storedPressure = autoContainer.type('pressure').variant('measured').position('upstream').get();
.variant('measured') console.log(` Stored upstream pressure: ${storedPressure.getCurrentValue()} ${storedPressure.unit}`);
.position('upstream') console.log(' Auto-conversion setup complete\n');
.get();
console.log(`Stored as: ${converted.getCurrentValue()} ${converted.unit} (distance=${converted.distance}m)`);
console.log('✅ Auto-conversion complete\n');
// ==================================== // ====================================
// UNIT CONVERSION ON RETRIEVAL // UNIT CONVERSION EXAMPLES
// ==================================== // ====================================
console.log('--- Example 3: Unit Conversion on Retrieval ---'); console.log('--- Example 3: Unit Conversion on Retrieval ---');
console.log('Getting values in different units without changing stored data...\n');
autoContainer // Add flow data in different units
.type('flow') autoContainer.type('flow').variant('predicted').position('upstream')
.variant('predicted') .value(100, Date.now(), 'l/min'); // Stored in l/min (default)
.position('upstream')
.distance(2.4)
.value(100, Date.now(), 'l/min');
const flowMeasurement = autoContainer autoContainer.type('flow').variant('predicted').position('downstream')
.type('flow') .value(6, Date.now(), 'm3/h'); // Auto-converted from m3/h to l/min
.variant('predicted')
.position('upstream')
.get();
console.log(`Flow in l/min: ${flowMeasurement.getCurrentValue('l/min')}`); // Retrieve the same data in different units
console.log(`Flow in m³/h: ${flowMeasurement.getCurrentValue('m3/h').toFixed(2)}`); const flowLPM = autoContainer.type('flow').variant('predicted').position('upstream').getCurrentValue('l/min');
console.log(`Flow in gal/min: ${flowMeasurement.getCurrentValue('gal/min').toFixed(2)}`); const flowM3H = autoContainer.type('flow').variant('predicted').position('upstream').getCurrentValue('m3/h');
console.log(`Distance: ${flowMeasurement.distance}m\n`); const flowGPM = autoContainer.type('flow').variant('predicted').position('upstream').getCurrentValue('gal/min');
console.log(`Flow in l/min: ${flowLPM}`);
console.log(`Flow in m³/h: ${flowM3H.toFixed(2)}`);
console.log(`Flow in gal/min: ${flowGPM.toFixed(2)}`);
console.log('Unit conversion examples complete\n');
// ==================================== // ====================================
// SMART UNIT SELECTION // SMART UNIT SELECTION
// ==================================== // ====================================
console.log('--- Example 4: Smart Unit Selection ---'); console.log('--- Example 4: Smart Unit Selection ---');
console.log('Automatically finding the best unit for readability...\n');
autoContainer // Add a very small pressure value
.type('pressure') autoContainer.type('pressure').variant('test').position('sensor')
.variant('test')
.position('sensor')
.distance(0.2)
.value(0.001, Date.now(), 'bar'); .value(0.001, Date.now(), 'bar');
const bestUnit = autoContainer // Get the best unit for this small value
.type('pressure') const bestUnit = autoContainer.type('pressure').variant('test').position('sensor').getBestUnit();
.variant('test')
.position('sensor')
.getBestUnit();
if (bestUnit) { if (bestUnit) {
console.log(`Best unit: ${bestUnit.val.toFixed(2)} ${bestUnit.unit}`); console.log(`Best unit representation: ${bestUnit.val} ${bestUnit.unit}`);
} }
// Get all available units for pressure
const availableUnits = autoContainer.getAvailableUnits('pressure'); const availableUnits = autoContainer.getAvailableUnits('pressure');
console.log(`Available units: ${availableUnits.slice(0, 5).join(', ')}...\n`); console.log(`Available pressure units: ${availableUnits.slice(0, 8).join(', ')}... (${availableUnits.length} total)`);
console.log('Smart unit selection complete\n');
// ==================================== // ====================================
// BASIC RETRIEVAL // BASIC RETRIEVAL AND CALCULATIONS
// ==================================== // ====================================
console.log('--- Example 5: Basic Value Retrieval ---'); console.log('--- Example 5: Basic Value Retrieval ---');
console.log('Getting individual values and their units...\n');
const upstreamVal = basicContainer // Using basic container for clear examples
.type('pressure') const upstreamValue = basicContainer.type('pressure').variant('measured').position('upstream').getCurrentValue();
.variant('measured') const upstreamUnit = basicContainer.type('pressure').variant('measured').position('upstream').get().unit;
.position('upstream') console.log(`Upstream pressure: ${upstreamValue} ${upstreamUnit}`);
.getCurrentValue();
const upstreamData = basicContainer const downstreamValue = basicContainer.type('pressure').variant('measured').position('downstream').getCurrentValue();
.type('pressure') const downstreamUnit = basicContainer.type('pressure').variant('measured').position('downstream').get().unit;
.variant('measured') console.log(`Downstream pressure: ${downstreamValue} ${downstreamUnit}`);
.position('upstream') console.log('Basic retrieval complete\n');
.get();
console.log(`Upstream: ${upstreamVal} ${upstreamData.unit} at ${upstreamData.distance}m`);
const downstreamVal = basicContainer
.type('pressure')
.variant('measured')
.position('downstream')
.getCurrentValue();
const downstreamData = basicContainer
.type('pressure')
.variant('measured')
.position('downstream')
.get();
//check wether a serie exists
const hasSeries = measurements
.type("flow")
.variant("measured")
.exists(); // true if any position exists
const hasUpstreamValues = measurements
.type("flow")
.variant("measured")
.exists({ position: "upstream", requireValues: true });
// Passing everything explicitly
const hasPercent = measurements.exists({
type: "volume",
variant: "percent",
position: "atEquipment",
});
console.log(`Downstream: ${downstreamVal} ${downstreamData.unit} at ${downstreamData.distance}m\n`);
// ==================================== // ====================================
// CALCULATIONS & STATISTICS // CALCULATIONS AND STATISTICS
// ==================================== // ====================================
console.log('--- Example 6: Calculations & Statistics ---'); console.log('--- Example 6: Calculations & Statistics ---');
console.log('Using built-in calculation methods...\n');
basicContainer // Add flow data for calculations
.type('flow') basicContainer.type('flow').variant('predicted').position('upstream').value(200).unit('gpm');
.variant('predicted') basicContainer.type('flow').variant('predicted').position('downstream').value(195).unit('gpm');
.position('upstream')
.distance(3.0)
.value(200)
.unit('gpm');
basicContainer const flowAvg = basicContainer.type('flow').variant('predicted').position('upstream').getAverage();
.type('flow') console.log(`Average upstream flow: ${flowAvg} gpm`);
.variant('predicted')
.position('downstream')
.distance(8.5)
.value(195)
.unit('gpm');
const flowAvg = basicContainer // Calculate pressure difference between upstream and downstream
.type('flow') const pressureDiff = basicContainer.type('pressure').variant('measured').difference();
.variant('predicted') console.log(`Pressure difference: ${pressureDiff.value} ${pressureDiff.unit}`);
.position('upstream') console.log('Calculations complete\n');
.getAverage();
console.log(`Average upstream flow: ${flowAvg.toFixed(1)} gpm`);
const pressureDiff = basicContainer
.type('pressure')
.variant('measured')
.difference();
console.log(`Pressure difference: ${pressureDiff.value} ${pressureDiff.unit}\n`);
//reversable difference
const deltaP = basicContainer.type("pressure").variant("measured").difference(); // defaults to downstream - upstream
const netFlow = basicContainer.type("flow").variant("measured").difference({ from: "upstream", to: "downstream" });
// ==================================== // ====================================
// ADVANCED STATISTICS & HISTORY // ADVANCED STATISTICS
// ==================================== // ====================================
console.log('--- Example 7: Advanced Statistics & History ---'); console.log('--- Example 7: Advanced Statistics & History ---');
console.log('Adding multiple values and getting comprehensive statistics...\n');
basicContainer // Add several flow measurements to build history
.type('flow') basicContainer.type('flow').variant('measured').position('upstream')
.variant('measured') .value(210).value(215).value(205).value(220).value(200).unit('m3/h');
.position('upstream') basicContainer.type('flow').variant('measured').position('downstream')
.distance(3.0) .value(190).value(195).value(185).value(200).value(180).unit('m3/h');
.value(210)
.value(215)
.value(205)
.value(220)
.value(200)
.unit('m3/h');
const stats = basicContainer
.type('flow')
.variant('measured')
.position('upstream');
const statsData = stats.get();
// Get comprehensive statistics
const measurement = basicContainer.type('flow').variant('measured').position('upstream');
console.log('Flow Statistics:'); console.log('Flow Statistics:');
console.log(` Current: ${stats.getCurrentValue()} ${statsData.unit}`); console.log(`- Current value: ${measurement.getCurrentValue()} ${measurement.get().unit}`);
console.log(` Average: ${stats.getAverage().toFixed(1)} ${statsData.unit}`); console.log(`- Average: ${measurement.getAverage().toFixed(1)} ${measurement.get().unit}`);
console.log(` Min: ${stats.getMin()} ${statsData.unit}`); console.log(`- Minimum: ${measurement.getMin()} ${measurement.get().unit}`);
console.log(` Max: ${stats.getMax()} ${statsData.unit}`); console.log(`- Maximum: ${measurement.getMax()} ${measurement.get().unit}`);
console.log(` Distance: ${statsData.distance}m`);
const allValues = stats.getAllValues(); // Show all values with timestamps
console.log(` Samples: ${allValues.values.length}`); const allValues = measurement.getAllValues();
console.log(` History: [${allValues.values.join(', ')}]\n`); console.log(`- Total samples: ${allValues.values.length}`);
console.log(`- Value history: [${allValues.values.join(', ')}]`);
console.log('--- Lagged sample comparison ---'); console.log('Advanced statistics complete\n');
const latest = stats.getCurrentValue(); // existing helper
const prevSample = stats.getLaggedValue(1); // new helper
const prevPrevSample = stats.getLaggedValue(2); // optional
if (prevSample) {
const delta = latest - prevSample.value;
console.log(
`Current vs previous: ${latest} ${statsData.unit} (t=${stats.get().getLatestTimestamp()}) vs ` +
`${prevSample.value} ${prevSample.unit} (t=${prevSample.timestamp})`
);
console.log(`Δ = ${delta.toFixed(2)} ${statsData.unit}`);
}
if (prevPrevSample) {
console.log(
`Previous vs 2-steps-back timestamps: ${new Date(prevSample.timestamp).toISOString()} vs ` +
`${new Date(prevPrevSample.timestamp).toISOString()}`
);
}
// ==================================== // ====================================
// DYNAMIC UNIT MANAGEMENT // DYNAMIC UNIT MANAGEMENT
// ==================================== // ====================================
console.log('--- Example 8: Dynamic Unit Management ---'); console.log('--- Example 8: Dynamic Unit Management ---');
console.log('Changing preferred units at runtime...\n');
// Change preferred unit for flow measurements
autoContainer.setPreferredUnit('flow', 'm3/h'); autoContainer.setPreferredUnit('flow', 'm3/h');
console.log('Changed preferred flow unit to m³/h'); console.log('Changed preferred flow unit to m³/h');
autoContainer // Add new flow data - will auto-convert to new preferred unit
.type('flow') autoContainer.type('flow').variant('realtime').position('inlet')
.variant('realtime') .value(150, Date.now(), 'l/min'); // Input in l/min, stored as m³/h
.position('inlet')
.distance(1.2)
.value(150, Date.now(), 'l/min');
const realtimeFlow = autoContainer const realtimeFlow = autoContainer.type('flow').variant('realtime').position('inlet');
.type('flow') console.log(`Stored as: ${realtimeFlow.getCurrentValue()} ${realtimeFlow.get().unit}`);
.variant('realtime') console.log(`Original unit: ${realtimeFlow.getCurrentValue('l/min')} l/min`);
.position('inlet') console.log('Dynamic unit management complete\n');
.get();
console.log(`Stored as: ${realtimeFlow.getCurrentValue()} ${realtimeFlow.unit}`);
console.log(`Original: ${realtimeFlow.getCurrentValue('l/min').toFixed(1)} l/min`);
console.log(`Distance: ${realtimeFlow.distance}m\n`);
// ==================================== // ====================================
// DATA EXPLORATION // DATA EXPLORATION
// ==================================== // ====================================
console.log('--- Example 9: Data Exploration ---'); console.log('--- Example 9: Data Exploration ---');
console.log('Discovering what data is available in the container...\n');
console.log('Available types:', basicContainer.getTypes()); console.log('Available measurement types:', basicContainer.getTypes());
console.log('Pressure variants:', basicContainer.type('pressure').getVariants()); console.log('Pressure variants:', basicContainer.type('pressure').getVariants());
console.log('Measured pressure positions:', basicContainer.type('pressure').variant('measured').getPositions()); console.log('Measured pressure positions:', basicContainer.type('pressure').variant('measured').getPositions());
console.log('\nData Structure:'); // Show data structure overview
console.log('\nData Structure Overview:');
basicContainer.getTypes().forEach(type => { basicContainer.getTypes().forEach(type => {
const variants = basicContainer.type(type).getVariants();
if (variants.length > 0) {
console.log(`${type.toUpperCase()}:`); console.log(`${type.toUpperCase()}:`);
const variants = basicContainer.type(type).getVariants();
variants.forEach(variant => { variants.forEach(variant => {
const positions = basicContainer.type(type).variant(variant).getPositions(); const positions = basicContainer.type(type).variant(variant).getPositions();
positions.forEach(position => { positions.forEach(position => {
const m = basicContainer.type(type).variant(variant).position(position).get(); const measurement = basicContainer.type(type).variant(variant).position(position).get();
if (m && m.values.length > 0) { if (measurement && measurement.values.length > 0) {
console.log(` ${variant}.${position}: ${m.values.length} values, ${m.unit || 'no unit'}, dist=${m.distance ?? 'n/a'}m`); console.log(` └─${variant}.${position}: ${measurement.values.length} values (${measurement.unit || 'no unit'})`);
} }
}); });
}); });
}
}); });
console.log('Data exploration complete\n');
console.log('\n✅ All examples complete!\n');
// ==================================== // ====================================
// BEST PRACTICES // BEST PRACTICES SUMMARY
// ==================================== // ====================================
console.log('--- Best Practices Summary ---\n'); console.log('--- Best Practices Summary ---');
console.log('BEST PRACTICES FOR NEW USERS:\n');
console.log('SETUP:'); console.log('1. SETUP:');
console.log(' • Enable autoConvert for consistent units'); console.log(' • Enable auto-conversion for consistent units');
console.log(' • Define defaultUnits for your measurement types'); console.log(' • Define default units for your measurement types');
console.log(' • Set windowSize based on your data retention needs\n'); console.log(' • Set appropriate window size for your data needs\n');
console.log('STORING DATA:'); console.log('2. STORING DATA:');
console.log(' • Chain methods: type().variant().position().distance().value()'); console.log(' Always use the full chain: type().variant().position().value()');
console.log(' • Set distance once - it persists for that position'); console.log(' • Specify source unit when adding values: .value(100, timestamp, "psi")');
console.log(' • Specify source unit: .value(100, timestamp, "psi")'); console.log(' • Set units immediately after first value: .value(100).unit("psi")\n');
console.log(' • Set unit immediately: .value(100).unit("psi")\n');
console.log('RETRIEVING DATA:'); console.log('3. RETRIEVING DATA:');
console.log(' • Use .getCurrentValue("unit") for specific units'); console.log(' • Use .getCurrentValue("unit") to get values in specific units');
console.log(' • Use .getBestUnit() for automatic selection'); console.log(' • Use .getBestUnit() for automatic unit selection');
console.log(' • Use .difference() for upstream/downstream deltas'); console.log(' • Use .difference() for automatic upstream/downstream calculations\n');
console.log(' • Access .get().distance for physical positioning\n');
console.log('MONITORING:'); console.log('4. MONITORING:');
console.log(' • Subscribe: .emitter.on("type.variant.position", callback)'); console.log(' • Subscribe to events for real-time updates');
console.log(' • Event data includes: value, unit, timestamp, distance'); console.log(' Use .emitter.on("type.variant.position", callback)');
console.log(' • Explore data: .getTypes(), .getVariants(), .getPositions()\n'); console.log(' • Explore available data with .getTypes(), .getVariants(), .getPositions()\n');
module.exports = { basicContainer, autoContainer }; console.log('All examples complete! Ready to use MeasurementContainer');
// Export for programmatic use
module.exports = {
runExamples: () => {
console.log('Measurement Container Examples - Complete Guide for New Users');
console.log('This file demonstrates all features with practical examples.');
},
// Export containers for testing
basicContainer,
autoContainer
};

View File

@@ -180,47 +180,19 @@ getSaveInjectionCode(nodeName) {
return ` return `
// PhysicalPosition Save injection for ${nodeName} // PhysicalPosition Save injection for ${nodeName}
window.EVOLV.nodes.${nodeName}.positionMenu.saveEditor = function(node) { window.EVOLV.nodes.${nodeName}.positionMenu.saveEditor = function(node) {
console.log("=== PhysicalPosition Save Debug ===");
const sel = document.getElementById('node-input-positionVsParent'); const sel = document.getElementById('node-input-positionVsParent');
const hasDistanceCheck = document.getElementById('node-input-hasDistance'); const hasDistanceCheck = document.getElementById('node-input-hasDistance');
const distanceInput = document.getElementById('node-input-distance'); const distanceInput = document.getElementById('node-input-distance');
console.log("→ sel element found:", !!sel);
console.log("→ sel:", sel);
console.log("→ sel.value:", sel ? sel.value : "NO ELEMENT");
console.log("→ sel.selectedIndex:", sel ? sel.selectedIndex : "NO ELEMENT");
console.log("→ sel.options:", sel ? Array.from(sel.options).map(o => ({value: o.value, text: o.textContent})) : "NO OPTIONS");
if (!sel) {
console.error("→ positionMenu.saveEditor FAILED: select element not found!");
return false;
}
// Save existing position data // Save existing position data
const positionValue = sel.value; node.positionVsParent = sel ? sel.value : 'atEquipment';
const selectedOption = sel.options[sel.selectedIndex]; node.positionLabel = sel ? sel.options[sel.selectedIndex].textContent : 'At Equipment';
node.positionIcon = sel ? sel.options[sel.selectedIndex].getAttribute('data-icon') : 'fa fa-cog';
console.log("→ positionValue:", positionValue);
console.log("→ selectedOption:", selectedOption);
console.log("→ selectedOption.textContent:", selectedOption ? selectedOption.textContent : "NO OPTION");
console.log("→ selectedOption data-icon:", selectedOption ? selectedOption.getAttribute('data-icon') : "NO ICON");
node.positionVsParent = positionValue || 'atEquipment';
node.positionLabel = selectedOption ? selectedOption.textContent : 'At Equipment';
node.positionIcon = selectedOption ? selectedOption.getAttribute('data-icon') : 'fa fa-cog';
console.log("→ node.positionVsParent set to:", node.positionVsParent);
console.log("→ node.positionLabel set to:", node.positionLabel);
// Save distance data
console.log("→ hasDistanceCheck found:", !!hasDistanceCheck);
console.log("→ hasDistanceCheck.checked:", hasDistanceCheck ? hasDistanceCheck.checked : "NO ELEMENT");
// Save distance data (NEW)
node.hasDistance = hasDistanceCheck ? hasDistanceCheck.checked : false; node.hasDistance = hasDistanceCheck ? hasDistanceCheck.checked : false;
if (node.hasDistance && distanceInput && distanceInput.value) { if (node.hasDistance && distanceInput && distanceInput.value) {
console.log("→ distanceInput.value:", distanceInput.value);
node.distance = parseFloat(distanceInput.value) || 0; node.distance = parseFloat(distanceInput.value) || 0;
node.distanceUnit = 'm'; // Fixed to meters for now node.distanceUnit = 'm'; // Fixed to meters for now
@@ -228,18 +200,13 @@ getSaveInjectionCode(nodeName) {
const contexts = window.EVOLV.nodes.${nodeName}.menuData.position.distanceContexts; const contexts = window.EVOLV.nodes.${nodeName}.menuData.position.distanceContexts;
const context = contexts && contexts[node.positionVsParent]; const context = contexts && contexts[node.positionVsParent];
node.distanceDescription = context ? context.description : 'Distance from parent'; node.distanceDescription = context ? context.description : 'Distance from parent';
console.log("→ distance set to:", node.distance);
} else { } else {
console.log("→ clearing distance data"); // Clear distance data if not specified
delete node.distance; delete node.distance;
delete node.distanceUnit; delete node.distanceUnit;
delete node.distanceDescription; delete node.distanceDescription;
} }
console.log("→ positionMenu.saveEditor result: SUCCESS");
console.log("→ final node.positionVsParent:", node.positionVsParent);
return true; return true;
}; };
`; `;

View File

@@ -1,7 +1,7 @@
{ {
"general": { "general": {
"name": { "name": {
"default": "interpolation configuration", "default": "Interpolation Configuration",
"rules": { "rules": {
"type": "string", "type": "string",
"description": "A human-readable name or label for this interpolation configuration." "description": "A human-readable name or label for this interpolation configuration."
@@ -70,7 +70,7 @@
} }
}, },
"role": { "role": {
"default": "interpolator", "default": "Interpolator",
"rules": { "rules": {
"type": "string", "type": "string",
"description": "Indicates the role of this configuration (e.g., 'Interpolator', 'DataCurve', etc.)." "description": "Indicates the role of this configuration (e.g., 'Interpolator', 'DataCurve', etc.)."

View File

@@ -350,7 +350,6 @@ class Predict {
} }
buildAllFxyCurves(curve) { buildAllFxyCurves(curve) {
let globalMinY = Infinity; let globalMinY = Infinity;
let globalMaxY = -Infinity; let globalMaxY = -Infinity;

View File

@@ -52,7 +52,6 @@ class state{
return this.stateManager.getRunTimeHours(); return this.stateManager.getRunTimeHours();
} }
async moveTo(targetPosition) { async moveTo(targetPosition) {
// Check for invalid conditions and throw errors // Check for invalid conditions and throw errors
@@ -87,33 +86,14 @@ class state{
// -------- State Transition Methods -------- // // -------- State Transition Methods -------- //
abortCurrentMovement(reason = "group override") {
if (this.abortController && !this.abortController.signal.aborted) {
this.logger.warn(`Aborting movement: ${reason}`);
this.abortController.abort();
}
}
async transitionToState(targetState, signal) { async transitionToState(targetState, signal) {
const fromState = this.getCurrentState(); const fromState = this.getCurrentState();
const position = this.getCurrentPosition(); const position = this.getCurrentPosition();
// Define states that cannot be aborted for safety reasons
const protectedStates = ['warmingup', 'coolingdown'];
const isProtectedTransition = protectedStates.includes(fromState);
try { try {
this.logger.debug(`Starting transition from ${fromState} to ${targetState}.`); this.logger.debug(`Starting transition from ${fromState} to ${targetState}.`);
if( isProtectedTransition){
//overrule signal to prevent abortion
signal = null; // Disable abortion for protected states
//spit warning
this.logger.warn(`Transition from ${fromState} to ${targetState} is protected and cannot be aborted.`);
}
// Await the state transition and pass signal for abortion
const feedback = await this.stateManager.transitionTo(targetState,signal); const feedback = await this.stateManager.transitionTo(targetState,signal);
this.logger.info(`Statemanager: ${feedback}`); this.logger.info(`Statemanager: ${feedback}`);
@@ -128,6 +108,7 @@ class state{
//trigger move //trigger move
await this.moveTo(this.delayedMove,signal); await this.moveTo(this.delayedMove,signal);
this.delayedMove = null; this.delayedMove = null;
this.logger.info(`moveTo : ${feedback} `); this.logger.info(`moveTo : ${feedback} `);
} }

View File

@@ -1,7 +1,7 @@
{ {
"general": { "general": {
"name": { "name": {
"default": "state configuration", "default": "State Configuration",
"rules": { "rules": {
"type": "string", "type": "string",
"description": "A human-readable name for the state configuration." "description": "A human-readable name for the state configuration."
@@ -65,7 +65,7 @@
} }
}, },
"role": { "role": {
"default": "statecontroller", "default": "StateController",
"rules": { "rules": {
"type": "string", "type": "string",
"description": "Functional role within the system." "description": "Functional role within the system."