feat(components): add ThermoState generators and Eurovent backend demo

This commit is contained in:
Sepehr
2026-02-20 22:01:38 +01:00
parent 375d288950
commit 4a40fddfe3
271 changed files with 28614 additions and 447 deletions

View File

@@ -0,0 +1,64 @@
//! Build script for coolprop-sys.
//!
//! This compiles the CoolProp C++ library statically.
use std::env;
use std::path::PathBuf;
fn coolprop_src_path() -> Option<PathBuf> {
// Try to find CoolProp source in common locations
let possible_paths = vec![
// Vendor directory (recommended)
PathBuf::from("vendor/coolprop"),
// External directory
PathBuf::from("external/coolprop"),
// System paths
PathBuf::from("/usr/local/src/CoolProp"),
PathBuf::from("/opt/CoolProp"),
];
for path in possible_paths {
if path.join("CMakeLists.txt").exists() {
return Some(path);
}
}
None
}
fn main() {
let static_linking = env::var("CARGO_FEATURE_STATIC").is_ok();
// Check if CoolProp source is available
if let Some(coolprop_path) = coolprop_src_path() {
println!("cargo:rerun-if-changed={}", coolprop_path.display());
// Configure build for CoolProp
println!(
"cargo:rustc-link-search=native={}/build",
coolprop_path.display()
);
}
// Link against CoolProp
if static_linking {
// Static linking - find libCoolProp.a
println!("cargo:rustc-link-lib=static=CoolProp");
} else {
// Dynamic linking
println!("cargo:rustc-link-lib=dylib=CoolProp");
}
// Link required system libraries
println!("cargo:rustc-link-lib=dylib=m");
println!("cargo:rustc-link-lib=dylib=stdc++");
// Tell Cargo to rerun if build.rs changes
println!("cargo:rerun-if-changed=build.rs");
println!(
"cargo:warning=CoolProp source not found in vendor/.
For full static build, run:
git clone https://github.com/CoolProp/CoolProp.git vendor/coolprop"
);
}

View File

@@ -0,0 +1,336 @@
//! FFI bindings to CoolProp C++ library.
//!
//! This module provides low-level FFI bindings to the CoolProp library.
//! All functions are unsafe and require proper error handling.
#![allow(dead_code)]
use libc::{c_char, c_double, c_int};
use std::ffi::CString;
/// Error codes returned by CoolProp
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum CoolPropError {
/// No error occurred
NoError = 0,
/// Input error code
InputError = 1,
/// Library not loaded
LibraryNotLoaded = 2,
/// Unknown property value
UnknownPropertyValue = 3,
/// Unknown fluid
UnknownFluid = 4,
/// Unknown parameter
UnknownParameter = 5,
/// Not implemented
NotImplemented = 6,
/// Invalid number of parameters
InvalidNumber = 7,
/// Could not load library
CouldNotLoadLibrary = 8,
/// Invalid fluid pair
InvalidFluidPair = 9,
/// Version mismatch
VersionMismatch = 10,
/// Internal error
InternalError = 11,
}
impl CoolPropError {
/// Convert CoolProp error code to Rust result
pub fn from_code(code: i32) -> Result<(), CoolPropError> {
match code {
0 => Ok(()),
_ => Err(match code {
1 => CoolPropError::InputError,
2 => CoolPropError::LibraryNotLoaded,
3 => CoolPropError::UnknownPropertyValue,
4 => CoolPropError::UnknownFluid,
5 => CoolPropError::UnknownParameter,
6 => CoolPropError::NotImplemented,
7 => CoolPropError::InvalidNumber,
8 => CoolPropError::CouldNotLoadLibrary,
9 => CoolPropError::InvalidFluidPair,
10 => CoolPropError::VersionMismatch,
_ => CoolPropError::InternalError,
}),
}
}
}
/// Output parameters for CoolProp
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum CoolPropParam {
/// Nothing
Nothing = 0,
/// Pressure [Pa]
Pressure = 1,
/// Temperature [K]
Temperature = 2,
/// Density [kg/m³]
Density = 3,
/// Specific enthalpy [J/kg]
Enthalpy = 4,
/// Specific entropy [J/kg/K]
Entropy = 5,
/// Specific internal energy [J/kg]
InternalEnergy = 6,
/// Specific heat at constant pressure [J/kg/K]
Cv = 7,
/// Specific heat at constant pressure [J/kg/K]
Cp = 8,
/// Quality [-]
Quality = 9,
/// Viscosity [Pa·s]
Viscosity = 10,
/// Thermal conductivity [W/m/K]
Conductivity = 11,
/// Surface tension [N/m]
SurfaceTension = 12,
/// Prandtl number [-]
Prandtl = 13,
}
/// Input parameters for CoolProp
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum CoolPropInputPair {
/// No input
None = 0,
/// Pressure & Temperature
PT = 1,
/// Pressure & Density
PD = 2,
/// Pressure & Enthalpy
PH = 3,
/// Pressure & Entropy
PS = 4,
/// Pressure & Internal Energy
PU = 5,
/// Temperature & Density
TD = 6,
/// Temperature & Enthalpy
TH = 7,
/// Temperature & Entropy
TS = 8,
/// Temperature & Internal Energy
TU = 9,
/// Enthalpy & Entropy
HS = 10,
/// Density & Internal Energy
DU = 11,
/// Pressure & Quality
PQ = 12,
/// Temperature & Quality
TQ = 13,
}
// CoolProp C functions
extern "C" {
/// Get a property value using pressure and temperature
fn CoolProp_PropsSI(
Output: c_char,
Name1: c_char,
Value1: c_double,
Name2: c_char,
Value2: c_double,
Fluid: *const c_char,
) -> c_double;
/// Get a property value using input pair
fn CoolProp_Props1SI(Fluid: *const c_char, Output: c_char) -> c_double;
/// Get CoolProp version string
fn CoolProp_get_global_param_string(
Param: *const c_char,
Output: *mut c_char,
OutputLength: c_int,
) -> c_int;
/// Get fluid info
fn CoolProp_get_fluid_param_string(
Fluid: *const c_char,
Param: *const c_char,
Output: *mut c_char,
OutputLength: c_int,
) -> c_int;
/// Check if fluid exists
fn CoolProp_isfluid(Fluid: *const c_char) -> c_int;
/// Get saturation temperature
fn CoolProp_Saturation_T(Fluid: *const c_char, Par: c_char, Value: c_double) -> c_double;
/// Get critical point
fn CoolProp_CriticalPoint(Fluid: *const c_char, Output: c_char) -> c_double;
}
/// Get a thermodynamic property using pressure and temperature.
///
/// # Arguments
/// * `property` - The property to retrieve (e.g., "D" for density, "H" for enthalpy)
/// * `p` - Pressure in Pa
/// * `t` - Temperature in K
/// * `fluid` - Fluid name (e.g., "R134a")
///
/// # Returns
/// The property value in SI units, or NaN if an error occurs
pub unsafe fn props_si_pt(property: &str, p: f64, t: f64, fluid: &str) -> f64 {
let prop = property.as_bytes()[0] as c_char;
let fluid_c = CString::new(fluid).unwrap();
CoolProp_PropsSI(prop, b'P' as c_char, p, b'T' as c_char, t, fluid_c.as_ptr())
}
/// Get a thermodynamic property using pressure and enthalpy.
///
/// # Arguments
/// * `property` - The property to retrieve
/// * `p` - Pressure in Pa
/// * `h` - Specific enthalpy in J/kg
/// * `fluid` - Fluid name
///
/// # Returns
/// The property value in SI units, or NaN if an error occurs
pub unsafe fn props_si_ph(property: &str, p: f64, h: f64, fluid: &str) -> f64 {
let prop = property.as_bytes()[0] as c_char;
let fluid_c = CString::new(fluid).unwrap();
CoolProp_PropsSI(prop, b'P' as c_char, p, b'H' as c_char, h, fluid_c.as_ptr())
}
/// Get a thermodynamic property using temperature and quality (saturation).
///
/// # Arguments
/// * `property` - The property to retrieve (D, H, S, P, etc.)
/// * `t` - Temperature in K
/// * `q` - Quality (0 = saturated liquid, 1 = saturated vapor)
/// * `fluid` - Fluid name
///
/// # Returns
/// The property value in SI units, or NaN if an error occurs
pub unsafe fn props_si_tq(property: &str, t: f64, q: f64, fluid: &str) -> f64 {
let prop = property.as_bytes()[0] as c_char;
let fluid_c = CString::new(fluid).unwrap();
CoolProp_PropsSI(prop, b'T' as c_char, t, b'Q' as c_char, q, fluid_c.as_ptr())
}
/// Get a thermodynamic property using pressure and quality.
///
/// # Arguments
/// * `property` - The property to retrieve
/// * `p` - Pressure in Pa
/// * `x` - Quality (0-1)
/// * `fluid` - Fluid name
///
/// # Returns
/// The property value in SI units, or NaN if an error occurs
pub unsafe fn props_si_px(property: &str, p: f64, x: f64, fluid: &str) -> f64 {
let prop = property.as_bytes()[0] as c_char;
let fluid_c = CString::new(fluid).unwrap();
CoolProp_PropsSI(
prop,
b'P' as c_char,
p,
b'Q' as c_char, // Q for quality
x,
fluid_c.as_ptr(),
)
}
/// Get critical point temperature for a fluid.
///
/// # Arguments
/// * `fluid` - Fluid name
///
/// # Returns
/// Critical temperature in K, or NaN if unavailable
pub unsafe fn critical_temperature(fluid: &str) -> f64 {
let fluid_c = CString::new(fluid).unwrap();
CoolProp_CriticalPoint(fluid_c.as_ptr(), b'T' as c_char)
}
/// Get critical point pressure for a fluid.
///
/// # Arguments
/// * `fluid` - Fluid name
///
/// # Returns
/// Critical pressure in Pa, or NaN if unavailable
pub unsafe fn critical_pressure(fluid: &str) -> f64 {
let fluid_c = CString::new(fluid).unwrap();
CoolProp_CriticalPoint(fluid_c.as_ptr(), b'P' as c_char)
}
/// Get critical point density for a fluid.
///
/// # Arguments
/// * `fluid` - Fluid name
///
/// # Returns
/// Critical density in kg/m³, or NaN if unavailable
pub unsafe fn critical_density(fluid: &str) -> f64 {
let fluid_c = CString::new(fluid).unwrap();
CoolProp_CriticalPoint(fluid_c.as_ptr(), b'D' as c_char)
}
/// Check if a fluid is available in CoolProp.
///
/// # Arguments
/// * `fluid` - Fluid name
///
/// # Returns
/// `true` if the fluid is available
pub unsafe fn is_fluid_available(fluid: &str) -> bool {
let fluid_c = CString::new(fluid).unwrap();
CoolProp_isfluid(fluid_c.as_ptr()) != 0
}
/// Get CoolProp version string.
///
/// # Returns
/// Version string (e.g., "6.14.0")
pub fn get_version() -> String {
unsafe {
let mut buffer = vec![0u8; 32];
let result = CoolProp_get_global_param_string(
b"version\0".as_ptr() as *const c_char,
buffer.as_mut_ptr() as *mut c_char,
buffer.len() as c_int,
);
if result == 0 {
String::from_utf8_lossy(&buffer)
.trim_end_matches('\0')
.to_string()
} else {
String::from("Unknown")
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_version() {
let version = get_version();
assert!(!version.is_empty());
}
#[test]
fn test_fluid_available() {
// Test some common refrigerants
unsafe {
assert!(is_fluid_available("R134a"));
assert!(is_fluid_available("R410A"));
assert!(is_fluid_available("CO2"));
}
}
}