feat: initial

This commit is contained in:
aZholtikov committed 2026-10-12 18:57:28 +03:00
1 parent d1fede526a
commit 8c0160dd77
16 files changed
+1307 -3

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
.DS_Store
heat_exchander_airflow_pressure_drop.dSYM
commit.md
+4
View File
@@ -0,0 +1,4 @@
{
"version": "0.2.0",
"configurations": []
}
+49
View File
@@ -0,0 +1,49 @@
{
"tasks": [
{
"type": "cppbuild",
"label": "C/C++: gcc build MAC",
"command": "/usr/bin/gcc",
"args": [
"-fdiagnostics-color=always",
"-g",
"-I${fileDirname}",
"${workspaceFolder}/*.c",
"${workspaceFolder}/ats0002/*.c",
"-o",
"${fileDirname}/${fileBasenameNoExtension}"
],
"options": {
"cwd": "${fileDirname}"
},
"problemMatcher": [
"$gcc"
],
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "C/C++: gcc build WIN",
"type": "shell",
"command": "x86_64-w64-mingw32-gcc",
"args": [
"-g",
"-Wall",
"-o",
"${workspaceFolder}/heat_exchander_airflow_pressure_drop.exe",
"${workspaceFolder}/*.c",
"${workspaceFolder}/ats0002/*.c"
],
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": [
"$gcc"
]
}
],
"version": "2.0.0"
}
-1
View File
@@ -1 +0,0 @@
idf_component_register(SRCS "main.c" INCLUDE_DIRS "include")
+25 -2
View File
@@ -1,3 +1,26 @@
# esp_component_template # Heat Exchanger Airflow Pressure Drop Calculation
esp_component_template ## Features
1. Calculation of pressure drop across heat exchangers.
2. Support for three heat exchanger models:
- Honeywell Heat Exchanger CMM 21-50-14
- Liebherr Reheater CMM 21-53-89
- Liebherr Condenser CMM 21-53-88
3. Multiple part number selection for each heat exchanger.
4. Air passage configuration selection.
5. Airflow and static inlet pressure measurement input.
6. Automatic pass/fail evaluation against max allowed pressure drop limit.
7. Recalculation support based on barometric pressure and airflow temperature.
8. Logarithmic interpolation of pressure drop limits from anchor points.
9. Support for KPA and PSIG pressure units.
## Using
Clone and build the project:
```text
git clone https://github.com/aZholtikov/ats0002
cd ats0002
build
```
+274
View File
@@ -0,0 +1,274 @@
#pragma once
#include "ats0002.h"
#ifdef __cplusplus
extern "C"
{
#endif
/**
* @file 21_50_14.h
*
* @brief Honeywell Heat Exchanger CMM 21-50-14 configuration data.
*
* This module provides static configuration data for Honeywell Heat Exchanger
* CMM 21-50-14, including pressure-to-airflow anchor points for
* cooling and hot sides, air passage definitions, and heat exchanger type
* mappings for part numbers 182820-1 through 182820-4.
*
* Key features:
* - Cooling side performance data (Figures 4002 and 4003)
* - Hot side performance data (Figures 4005 and 4006)
* - Complete air passage configuration with operational limits
*
* @note All data arrays are static and const-qualified for read-only access.
*/
/**
* @brief Honeywell Heat Exchanger CMM 21-50-14, Figure 4002 (P/N 182820-1).
*
* Cooling side dependence of ΔPS (KPA) on air flow rate (LB/MIN), UPPER LIMIT.
*/
static const airflow_to_pressure_anchors_data_t _cooling_side_182820_1[] = {
{30.0, 0.14},
{40.0, 0.23},
{50.0, 0.33},
{60.0, 0.45},
{70.0, 0.57},
{80.0, 0.72},
{90.0, 0.87},
{100.0, 1.07},
{200.0, 3.48},
{300.0, 6.72}};
/**
* @brief Honeywell Heat Exchanger CMM 21-50-14, Figure 4002 (P/N 182820-2).
*
* Cooling side dependence of ΔPS (KPA) on air flow rate (LB/MIN), UPPER LIMIT.
*/
static const airflow_to_pressure_anchors_data_t _cooling_side_182820_2[] = {
{30.0, 0.14},
{40.0, 0.23},
{50.0, 0.33},
{60.0, 0.45},
{70.0, 0.57},
{80.0, 0.72},
{90.0, 0.87},
{100.0, 1.07},
{200.0, 3.48},
{300.0, 6.72}};
/**
* @brief Honeywell Heat Exchanger CMM 21-50-14, Figure 4003 (P/N 182820-3).
*
* Cooling side dependence of ΔPS (KPA) on air flow rate (LB/MIN), UPPER LIMIT.
*/
static const airflow_to_pressure_anchors_data_t _cooling_side_182820_3[] = {
{30.0, 0.12},
{40.0, 0.19},
{50.0, 0.29},
{60.0, 0.39},
{70.0, 0.47},
{80.0, 0.60},
{90.0, 0.72},
{100.0, 0.85},
{200.0, 2.59},
{300.0, 4.85},
{400.0, 7.96}};
/**
* @brief Honeywell Heat Exchanger CMM 21-50-14, Figure 4003 (P/N 182820-4).
*
* Cooling side dependence of ΔPS (KPA) on air flow rate (LB/MIN), UPPER LIMIT.
*/
static const airflow_to_pressure_anchors_data_t _cooling_side_182820_4[] = {
{30.0, 0.12},
{40.0, 0.19},
{50.0, 0.29},
{60.0, 0.39},
{70.0, 0.47},
{80.0, 0.60},
{90.0, 0.72},
{100.0, 0.85},
{200.0, 2.59},
{300.0, 4.85},
{400.0, 7.96}};
/**
* @brief Honeywell Heat Exchanger CMM 21-50-14, Figure 4005 (P/N 182820-1).
*
* Hot side dependence of ΔPS (PSIG) on air flow rate (LB/MIN), UPPER LIMIT.
*/
static const airflow_to_pressure_anchors_data_t _hot_side_182820_1[] = {
{30.0, 1.18},
{40.0, 1.96},
{50.0, 3.05},
{60.0, 4.13},
{70.0, 5.65},
{80.0, 7.02},
{90.0, 8.94},
{100.0, 10.81},
{200.0, 43.22}};
/**
* @brief Honeywell Heat Exchanger CMM 21-50-14, Figure 4005 (P/N 182820-2).
*
* Hot side dependence of ΔPS (PSIG) on air flow rate (LB/MIN), UPPER LIMIT.
*/
static const airflow_to_pressure_anchors_data_t _hot_side_182820_2[] = {
{30.0, 1.18},
{40.0, 1.96},
{50.0, 3.05},
{60.0, 4.13},
{70.0, 5.65},
{80.0, 7.02},
{90.0, 8.94},
{100.0, 10.81},
{200.0, 43.22}};
/**
* @brief Honeywell Heat Exchanger CMM 21-50-14, Figure 4006 (P/N 182820-3).
*
* Hot side dependence of ΔPS (PSIG) on air flow rate (LB/MIN), UPPER LIMIT.
*/
static const airflow_to_pressure_anchors_data_t _hot_side_182820_3[] = {
{30.0, 1.67},
{40.0, 2.65},
{50.0, 3.93},
{60.0, 5.60},
{70.0, 7.17},
{80.0, 9.33},
{90.0, 11.30},
{100.0, 13.75},
{200.0, 44.20}};
/**
* @brief Honeywell Heat Exchanger CMM 21-50-14, Figure 4006 (P/N 182820-4).
*
* Hot side dependence of ΔPS (PSIG) on air flow rate (LB/MIN), UPPER LIMIT.
*/
static const airflow_to_pressure_anchors_data_t _hot_side_182820_4[] = {
{30.0, 1.67},
{40.0, 2.65},
{50.0, 3.93},
{60.0, 5.60},
{70.0, 7.17},
{80.0, 9.33},
{90.0, 11.30},
{100.0, 13.75},
{200.0, 44.20}};
static const air_passage_data_t _air_passage_182820_1_data[] = {
{.air_passage_description = "Cooling Air Side",
.air_passage_anchors = _cooling_side_182820_1,
.anchors_data_size = ARRAY_SIZE(_cooling_side_182820_1),
.recalculated_required = true,
.airflow_pressure_unit = KPA,
.min_barometric_pressure = 840.0,
.max_barometric_pressure = 1070.0,
.min_airflow_temperature = 15.0,
.max_airflow_temperature = 35.0,
.min_airflow_pressure = 0.01,
.max_airflow_pressure = 2.0},
{.air_passage_description = "Bleed Air Side",
.air_passage_anchors = _hot_side_182820_1,
.anchors_data_size = ARRAY_SIZE(_hot_side_182820_1),
.recalculated_required = true,
.airflow_pressure_unit = PSIG,
.min_barometric_pressure = 840.0,
.max_barometric_pressure = 1070.0,
.min_airflow_temperature = 15.0,
.max_airflow_temperature = 35.0,
.min_airflow_pressure = 0.01,
.max_airflow_pressure = 15.0}};
static const air_passage_data_t _air_passage_182820_2_data[] = {
{.air_passage_description = "Cooling Air Side",
.air_passage_anchors = _cooling_side_182820_2,
.anchors_data_size = ARRAY_SIZE(_cooling_side_182820_2),
.recalculated_required = true,
.airflow_pressure_unit = KPA,
.min_barometric_pressure = 840.0,
.max_barometric_pressure = 1070.0,
.min_airflow_temperature = 15.0,
.max_airflow_temperature = 35.0,
.min_airflow_pressure = 0.01,
.max_airflow_pressure = 2.0},
{.air_passage_description = "Bleed Air Side",
.air_passage_anchors = _hot_side_182820_2,
.anchors_data_size = ARRAY_SIZE(_hot_side_182820_2),
.recalculated_required = true,
.airflow_pressure_unit = PSIG,
.min_barometric_pressure = 840.0,
.max_barometric_pressure = 1070.0,
.min_airflow_temperature = 15.0,
.max_airflow_temperature = 35.0,
.min_airflow_pressure = 0.01,
.max_airflow_pressure = 15.0}};
static const air_passage_data_t _air_passage_182820_3_data[] = {
{.air_passage_description = "Cooling Air Side",
.air_passage_anchors = _cooling_side_182820_3,
.anchors_data_size = ARRAY_SIZE(_cooling_side_182820_3),
.recalculated_required = true,
.airflow_pressure_unit = KPA,
.min_barometric_pressure = 840.0,
.max_barometric_pressure = 1070.0,
.min_airflow_temperature = 15.0,
.max_airflow_temperature = 35.0,
.min_airflow_pressure = 0.01,
.max_airflow_pressure = 2.0},
{.air_passage_description = "Bleed Air Side",
.air_passage_anchors = _hot_side_182820_3,
.anchors_data_size = ARRAY_SIZE(_hot_side_182820_3),
.recalculated_required = true,
.airflow_pressure_unit = PSIG,
.min_barometric_pressure = 840.0,
.max_barometric_pressure = 1070.0,
.min_airflow_temperature = 15.0,
.max_airflow_temperature = 35.0,
.min_airflow_pressure = 0.01,
.max_airflow_pressure = 15.0}};
static const air_passage_data_t _air_passage_182820_4_data[] = {
{.air_passage_description = "Cooling Air Side",
.air_passage_anchors = _cooling_side_182820_4,
.anchors_data_size = ARRAY_SIZE(_cooling_side_182820_4),
.recalculated_required = true,
.airflow_pressure_unit = KPA,
.min_barometric_pressure = 840.0,
.max_barometric_pressure = 1070.0,
.min_airflow_temperature = 15.0,
.max_airflow_temperature = 35.0,
.min_airflow_pressure = 0.01,
.max_airflow_pressure = 2.0},
{.air_passage_description = "Bleed Air Side",
.air_passage_anchors = _hot_side_182820_4,
.anchors_data_size = ARRAY_SIZE(_hot_side_182820_4),
.recalculated_required = true,
.airflow_pressure_unit = PSIG,
.min_barometric_pressure = 840.0,
.max_barometric_pressure = 1070.0,
.min_airflow_temperature = 15.0,
.max_airflow_temperature = 35.0,
.min_airflow_pressure = 0.01,
.max_airflow_pressure = 15.0}};
static const heat_exchanger_type_data_t heat_exchanger_21_50_14_data[] = {
{.part_number_description = "182820-1",
.air_passages_data = _air_passage_182820_1_data,
.air_passages_data_size = ARRAY_SIZE(_air_passage_182820_1_data)},
{.part_number_description = "182820-2",
.air_passages_data = _air_passage_182820_2_data,
.air_passages_data_size = ARRAY_SIZE(_air_passage_182820_2_data)},
{.part_number_description = "182820-3",
.air_passages_data = _air_passage_182820_3_data,
.air_passages_data_size = ARRAY_SIZE(_air_passage_182820_3_data)},
{.part_number_description = "182820-4",
.air_passages_data = _air_passage_182820_4_data,
.air_passages_data_size = ARRAY_SIZE(_air_passage_182820_4_data)}};
#ifdef __cplusplus
}
#endif
+154
View File
@@ -0,0 +1,154 @@
#pragma once
#include "ats0002.h"
#ifdef __cplusplus
extern "C"
{
#endif
/**
* @file 21_53_88.h
*
* @brief Liebherr Condenser CMM 21-53-88 configuration data.
*
* This module provides static configuration data for Liebherr Condenser CMM
* 21-53-88 heat exchanger, including pressure-to-airflow anchor points for
* cooling and hot sides, air passage definitions, and heat exchanger type
* mappings for part numbers 969A0000-01 and 969A0000-02.
*
* Key features:
* - Cooling side performance data (Figure 4002)
* - Hot side performance data (Figures 4003 and 4004)
* - Complete air passage configuration with operational limits
*
* @note All data arrays are static and const-qualified for read-only access.
*/
/**
* @brief Liebherr Condenser CMM 21-53-88, Figure 4002 (P/N 969A0000-01).
*
* Cooling side dependence of ΔPS (KPA) on air flow rate (LB/MIN), UPPER line (+20%).
*/
static const airflow_to_pressure_anchors_data_t _cooling_side_969A0000_01[] = {
{25.0, 0.26},
{30.0, 0.36},
{40.0, 0.61},
{50.0, 0.91},
{60.0, 1.26},
{70.0, 1.75},
{80.0, 2.33},
{90.0, 2.80},
{100.0, 3.50},
{200.0, 13.20}};
/**
* @brief Liebherr Condenser CMM 21-53-88, Figure 4002 (P/N 969A0000-02).
*
* Cooling side dependence of ΔPS (KPA) on air flow rate (LB/MIN), UPPER line (+20%).
*/
static const airflow_to_pressure_anchors_data_t _cooling_side_969A0000_02[] = {
{25.0, 0.26},
{30.0, 0.36},
{40.0, 0.61},
{50.0, 0.91},
{60.0, 1.26},
{70.0, 1.75},
{80.0, 2.33},
{90.0, 2.80},
{100.0, 3.50},
{200.0, 13.20}};
/**
* @brief Liebherr Condenser CMM 21-53-88, Figure 4003 (P/N 969A0000-01).
*
* Hot side dependence of ΔPS (PSIG) on air flow rate (LB/MIN), UPPER line (+20%).
*/
static const airflow_to_pressure_anchors_data_t _hot_side_969A0000_01[] = {
{20.0, 0.25},
{30.0, 0.48},
{40.0, 0.80},
{50.0, 1.25},
{60.0, 1.67},
{70.0, 2.25},
{80.0, 2.75},
{90.0, 3.57},
{100.0, 4.70},
{120.0, 11.00}};
/**
* @brief Liebherr Condenser CMM 21-53-88, Figure 4004 (P/N 969A0000-02).
*
* Hot side dependence of ΔPS (PSIG) on air flow rate (LB/MIN), UPPER line (+20%).
*/
static const airflow_to_pressure_anchors_data_t _hot_side_969A0000_02[] = {
{20.0, 0.17},
{30.0, 0.34},
{40.0, 0.57},
{50.0, 0.83},
{60.0, 1.18},
{70.0, 1.56},
{80.0, 1.93},
{90.0, 2.55},
{100.0, 3.42},
{120.0, 9.51}};
static const air_passage_data_t _air_passage_969A0000_01_data[] = {
{.air_passage_description = "Cold Air Side",
.air_passage_anchors = _cooling_side_969A0000_01,
.anchors_data_size = ARRAY_SIZE(_cooling_side_969A0000_01),
.recalculated_required = false,
.airflow_pressure_unit = KPA,
.min_barometric_pressure = 840.0,
.max_barometric_pressure = 1070.0,
.min_airflow_temperature = 15.0,
.max_airflow_temperature = 33.0,
.min_airflow_pressure = 0.01,
.max_airflow_pressure = 2.0},
{.air_passage_description = "Hot Air Side",
.air_passage_anchors = _hot_side_969A0000_01,
.anchors_data_size = ARRAY_SIZE(_hot_side_969A0000_01),
.recalculated_required = false,
.airflow_pressure_unit = PSIG,
.min_barometric_pressure = 840.0,
.max_barometric_pressure = 1070.0,
.min_airflow_temperature = 15.0,
.max_airflow_temperature = 33.0,
.min_airflow_pressure = 0.01,
.max_airflow_pressure = 15.0}};
static const air_passage_data_t _air_passage_969A0000_02_data[] = {
{.air_passage_description = "Cold Air Side",
.air_passage_anchors = _cooling_side_969A0000_02,
.anchors_data_size = ARRAY_SIZE(_cooling_side_969A0000_02),
.recalculated_required = false,
.airflow_pressure_unit = KPA,
.min_barometric_pressure = 840.0,
.max_barometric_pressure = 1070.0,
.min_airflow_temperature = 15.0,
.max_airflow_temperature = 33.0,
.min_airflow_pressure = 0.01,
.max_airflow_pressure = 2.0},
{.air_passage_description = "Hot Air Side",
.air_passage_anchors = _hot_side_969A0000_02,
.anchors_data_size = ARRAY_SIZE(_hot_side_969A0000_02),
.recalculated_required = false,
.airflow_pressure_unit = PSIG,
.min_barometric_pressure = 840.0,
.max_barometric_pressure = 1070.0,
.min_airflow_temperature = 15.0,
.max_airflow_temperature = 33.0,
.min_airflow_pressure = 0.01,
.max_airflow_pressure = 15.0}};
static const heat_exchanger_type_data_t heat_exchanger_21_53_88_data[] = {
{.part_number_description = "969A0000-01",
.air_passages_data = _air_passage_969A0000_01_data,
.air_passages_data_size = ARRAY_SIZE(_air_passage_969A0000_01_data)},
{.part_number_description = "969A0000-02",
.air_passages_data = _air_passage_969A0000_02_data,
.air_passages_data_size = ARRAY_SIZE(_air_passage_969A0000_02_data)}};
#ifdef __cplusplus
}
#endif
+154
View File
@@ -0,0 +1,154 @@
#pragma once
#include "ats0002.h"
#ifdef __cplusplus
extern "C"
{
#endif
/**
* @file 21_53_89.h
*
* @brief Liebherr Reheater CMM 21-53-89 configuration data.
*
* This module provides static configuration data for Liebherr Reheater CMM
* 21-53-89 heat exchanger, including pressure-to-airflow anchor points for
* cooling and hot sides, air passage definitions, and heat exchanger type
* mappings for part numbers 968B0000-01 and 968A0000-03.
*
* Key features:
* - Cooling side performance data (Figures 4002 and 4003)
* - Hot side performance data (Figures 4004 and 4005)
* - Complete air passage configuration with operational limits
*
* @note All data arrays are static and const-qualified for read-only access.
*/
/**
* @brief Liebherr Reheater CMM 21-53-89, Figure 4002 (P/N 968B0000-01).
*
* Cooling side dependence of ΔPS (PSIG) on air flow rate (LB/MIN), UPPER line (+20%).
*/
static const airflow_to_pressure_anchors_data_t _cooling_side_968B0000_01[] = {
{20.0, 0.11},
{30.0, 0.22},
{40.0, 0.37},
{50.0, 0.55},
{60.0, 0.78},
{70.0, 1.00},
{80.0, 1.40},
{90.0, 1.70},
{100.0, 2.00},
{200.0, 7.00}};
/**
* @brief Liebherr Reheater CMM 21-53-89, Figure 4003 (P/N 968A0000-03).
*
* Cooling side dependence of ΔPS (PSIG) on air flow rate (LB/MIN), UPPER line (+20%).
*/
static const airflow_to_pressure_anchors_data_t _cooling_side_968A0000_03[] = {
{20.0, 0.09},
{30.0, 0.19},
{40.0, 0.32},
{50.0, 0.47},
{60.0, 0.65},
{70.0, 0.85},
{80.0, 1.10},
{90.0, 1.40},
{100.0, 1.70},
{200.0, 5.60}};
/**
* @brief Liebherr Reheater CMM 21-53-89, Figure 4004 (P/N 968B0000-01).
*
* Hot side dependence of ΔPS (PSIG) on air flow rate (LB/MIN), UPPER line (+20%).
*/
static const airflow_to_pressure_anchors_data_t _hot_side_968B0000_01[] = {
{20.0, 0.09},
{30.0, 0.19},
{40.0, 0.32},
{50.0, 0.47},
{60.0, 0.65},
{70.0, 0.85},
{80.0, 1.10},
{90.0, 1.40},
{100.0, 1.70},
{200.0, 5.60}};
/**
* @brief Liebherr Reheater CMM 21-53-89, Figure 4005 (P/N 968A0000-03).
*
* Hot side dependence of ΔPS (PSIG) on air flow rate (LB/MIN), UPPER line (+20%).
*/
static const airflow_to_pressure_anchors_data_t _hot_side_968A0000_03[] = {
{20.0, 0.08},
{30.0, 0.17},
{40.0, 0.27},
{50.0, 0.40},
{60.0, 0.55},
{70.0, 0.72},
{80.0, 0.91},
{90.0, 1.25},
{100.0, 1.45},
{200.0, 4.70}};
static const air_passage_data_t _air_passage_968B0000_01_data[] = {
{.air_passage_description = "Cold Air Side",
.air_passage_anchors = _cooling_side_968B0000_01,
.anchors_data_size = ARRAY_SIZE(_cooling_side_968B0000_01),
.recalculated_required = false,
.airflow_pressure_unit = PSIG,
.min_barometric_pressure = 840.0,
.max_barometric_pressure = 1070.0,
.min_airflow_temperature = 15.0,
.max_airflow_temperature = 35.0,
.min_airflow_pressure = 0.01,
.max_airflow_pressure = 15.0},
{.air_passage_description = "Hot Air Side",
.air_passage_anchors = _hot_side_968B0000_01,
.anchors_data_size = ARRAY_SIZE(_hot_side_968B0000_01),
.recalculated_required = false,
.airflow_pressure_unit = PSIG,
.min_barometric_pressure = 840.0,
.max_barometric_pressure = 1070.0,
.min_airflow_temperature = 15.0,
.max_airflow_temperature = 35.0,
.min_airflow_pressure = 0.01,
.max_airflow_pressure = 15.0}};
static const air_passage_data_t _air_passage_968A0000_03_data[] = {
{.air_passage_description = "Cold Air Side",
.air_passage_anchors = _cooling_side_968A0000_03,
.anchors_data_size = ARRAY_SIZE(_cooling_side_968A0000_03),
.recalculated_required = false,
.airflow_pressure_unit = PSIG,
.min_barometric_pressure = 840.0,
.max_barometric_pressure = 1070.0,
.min_airflow_temperature = 15.0,
.max_airflow_temperature = 35.0,
.min_airflow_pressure = 0.01,
.max_airflow_pressure = 15.0},
{.air_passage_description = "Hot Air Side",
.air_passage_anchors = _hot_side_968A0000_03,
.anchors_data_size = ARRAY_SIZE(_hot_side_968A0000_03),
.recalculated_required = false,
.airflow_pressure_unit = PSIG,
.min_barometric_pressure = 840.0,
.max_barometric_pressure = 1070.0,
.min_airflow_temperature = 15.0,
.max_airflow_temperature = 35.0,
.min_airflow_pressure = 0.01,
.max_airflow_pressure = 15.0}};
static const heat_exchanger_type_data_t heat_exchanger_21_53_89_data[] = {
{.part_number_description = "968B0000-01",
.air_passages_data = _air_passage_968B0000_01_data,
.air_passages_data_size = ARRAY_SIZE(_air_passage_968B0000_01_data)},
{.part_number_description = "968A0000-03",
.air_passages_data = _air_passage_968A0000_03_data,
.air_passages_data_size = ARRAY_SIZE(_air_passage_968A0000_03_data)}};
#ifdef __cplusplus
}
#endif
+333
View File
@@ -0,0 +1,333 @@
#include "ats0002.h"
/**
* @brief Normalize the decimal separator in a string from comma to dot.
*
* Replaces all comma characters with dots in-place, supporting locales
* that use comma as the decimal separator.
*
* @param str Pointer to the string to normalize (must not be NULL)
*/
static void _normalize_decimal_separator(char *str);
/**
* @brief Get a numeric value or special commands from user input.
*
* Reads a line from standard input, handles EOF and special commands
* ('q'/'Q' for EXIT, 'r'/'R' for REPEAT), normalizes the decimal
* separator, and converts the input to a double value.
*
* @param data Pointer to store the parsed numeric value or EXIT/REPEAT
*/
static void _get_user_input(double *data);
/**
* @brief Get a menu selection from user input with validation.
*
* Prompts the user for a selection, validates that the value is within
* the specified range, and handles EXIT and REPEAT commands.
*
* @param result Pointer to store the selected value (must not be NULL)
* @param size Number of available options
* @param prompt Prompt message to display to the user
* @param error_msg Error message to display for invalid input
*/
static void _get_selection(uint16_t *result, uint8_t size, const char *prompt, const char *error_msg);
/**
* @brief Get a double value from user input within a specified range.
*
* Prompts the user for a value, validates it is within the given range,
* and handles EXIT and REPEAT commands. Supports REPEAT for re-prompting
* without terminating.
*
* @param result Pointer to store the entered value (must not be NULL)
* @param min Minimum allowed value
* @param max Maximum allowed value
* @param prompt Prompt message to display to the user
* @param error_msg Error message to display for invalid input
*/
static void _get_double_in_range(double *result, double min, double max, const char *prompt, const char *error_msg);
/**
* @brief Get a log-interpolated pressure drop limit based on airflow.
*
* Reads a pressure drop limit from the user and calculates the interpolated
* value using logarithmic interpolation on the anchor points from the
* provided air passage data.
*
* @param limit Pointer to store the calculated pressure drop limit
* @param airflow Pointer to the current airflow value
* @param data Pointer to air passage data containing anchor points (must not be NULL)
*/
static void _get_log_interpolated_airflow_pressure_drop_limit(double *limit, const double *airflow, const air_passage_data_t *data);
/**
* @brief Calculate density-corrected static inlet pressure.
*
* Applies barometric pressure and temperature compensation to the raw
* airflow pressure value, producing a density-corrected result in HPA
* (hectopascal) units.
*
* @param corrected_airflow_pressure Pointer to store the corrected pressure
* @param airflow_pressure Pointer to the raw static inlet pressure value
* @param barometric_pressure Pointer to the current barometric pressure
* @param airflow_temperature Pointer to the current air temperature in Celsius
*/
static void _calculate_density_corrected_pressure(double *corrected_airflow_pressure, const double *airflow_pressure, const double *barometric_pressure, const double *airflow_temperature);
void get_user_command(uint16_t *user_command)
{
printf("Enter 'q' to quit or 'r' to REPEAT:\n");
for (;;)
{
double input = 0.0;
_get_user_input(&input);
if (input == (double)EXIT)
{
*user_command = EXIT;
return;
}
if (input == (double)REPEAT)
{
*user_command = REPEAT;
return;
}
printf("Incorrect command. Try again.\n");
}
}
static void _get_selection(uint16_t *result, uint8_t size, const char *prompt, const char *error_msg)
{
printf("%s (or 'q' to quit):\n", prompt);
for (;;)
{
double input = 0.0;
_get_user_input(&input);
if (input == (double)EXIT)
{
*result = EXIT;
return;
}
if (input >= 1.0 && input <= (double)size)
{
uint16_t val = (uint16_t)input;
if ((double)val == input)
{
*result = val - 1;
return;
}
}
printf("%s\n", error_msg);
}
}
void get_heat_exchanger_number(uint16_t *num, uint8_t size)
{
_get_selection(num, size, "Enter the heat exchanger number", "Incorrect heat exchanger number. Try again.");
}
void get_heat_exchanger_part_number(uint16_t *num, uint8_t size)
{
_get_selection(num, size, "Enter the heat exchanger part number", "Incorrect heat exchanger part number. Try again.");
}
void get_heat_exchanger_air_passage_number(uint16_t *num, uint8_t size)
{
_get_selection(num, size, "Enter the heat exchanger air passage number", "Incorrect heat exchanger air passage number. Try again.");
}
static void _get_double_in_range(double *result, double min, double max, const char *prompt, const char *error_msg)
{
printf("%s (or 'q' to quit):\n", prompt);
for (;;)
{
double input = 0.0;
_get_user_input(&input);
if (input == (double)EXIT)
{
*result = (double)EXIT;
return;
}
if (input == (double)REPEAT)
{
printf("%s\n", error_msg);
continue;
}
if (input >= min && input <= max)
{
*result = input;
return;
}
printf("%s\n", error_msg);
}
}
void get_airflow(double *airflow, const air_passage_data_t *data)
{
double min = data->air_passage_anchors[0].airflow;
double max = data->air_passage_anchors[data->anchors_data_size - 1].airflow;
_get_double_in_range(airflow, min, max, "Enter airflow in LB/MIN", "Incorrect airflow. Try again.");
}
void get_airflow_pressure(double *airflow_pressure, const air_passage_data_t *data)
{
double min = data->min_airflow_pressure;
double max = data->max_airflow_pressure;
const char *prompt = (data->airflow_pressure_unit == KPA) ? "Enter static inlet pressure in KPA" : "Enter static inlet pressure in PSIG";
_get_double_in_range(airflow_pressure, min, max, prompt, "Incorrect static inlet pressure. Try again.");
}
void get_barometric_pressure(double *barometric_pressure, const air_passage_data_t *data)
{
double min = data->min_barometric_pressure;
double max = data->max_barometric_pressure;
_get_double_in_range(barometric_pressure, min, max, "Enter barometric pressure in MBAR", "Incorrect barometric pressure. Try again.");
}
void get_airflow_temperature(double *airflow_temperature, const air_passage_data_t *data)
{
double min = data->min_airflow_temperature;
double max = data->max_airflow_temperature;
_get_double_in_range(airflow_temperature, min, max, "Enter air temperature in C", "Incorrect air temperature. Try again.");
}
void get_pressure_drop(double *pressure_drop_limit, const double *airflow, const double *airflow_pressure, double *corrected_airflow_pressure, const double *barometric_pressure, const double *airflow_temperature, const air_passage_data_t *data)
{
if (data->recalculated_required == true)
{
double hpa_pressure = 0.0;
double hpa_corrected_pressure = 0.0;
switch (data->airflow_pressure_unit)
{
case KPA:
hpa_pressure = *airflow_pressure * 10.0;
_calculate_density_corrected_pressure(&hpa_corrected_pressure, &hpa_pressure, barometric_pressure, airflow_temperature);
*corrected_airflow_pressure = hpa_corrected_pressure / 10.0;
break;
case PSIG:
hpa_pressure = *airflow_pressure * 68.9476;
_calculate_density_corrected_pressure(&hpa_corrected_pressure, &hpa_pressure, barometric_pressure, airflow_temperature);
*corrected_airflow_pressure = hpa_corrected_pressure * 0.0145038;
break;
default:
break;
}
}
_get_log_interpolated_airflow_pressure_drop_limit(pressure_drop_limit, airflow, data);
}
static void _normalize_decimal_separator(char *str)
{
char *src = str;
char *dst = str;
while (*src != '\0')
{
if (*src == ',')
{
*dst++ = '.';
src++;
}
else
{
*dst++ = *src++;
}
}
*dst = '\0';
}
static void _get_user_input(double *data)
{
char input[64];
for (;;)
{
printf("> ");
fflush(stdout);
if (fgets(input, sizeof(input), stdin) == NULL)
{
if (feof(stdin))
{
*data = EXIT;
return;
}
printf("Input error. Try again.\n");
continue;
}
if (strchr(input, '\n') == NULL)
{
int c;
while ((c = getchar()) != '\n' && c != EOF)
{
}
}
input[strcspn(input, "\n")] = '\0';
if (strcmp(input, "q") == 0 || strcmp(input, "Q") == 0)
{
*data = EXIT;
break;
}
if (strcmp(input, "r") == 0 || strcmp(input, "R") == 0)
{
*data = REPEAT;
break;
}
_normalize_decimal_separator(input);
char *endptr = NULL;
*data = strtod(input, &endptr);
if (endptr == input)
{
printf("Error: Not a valid number. Try again.\n");
continue;
}
while (*endptr == ' ')
{
endptr++;
}
if (*endptr != '\0')
{
printf("Error: Unexpected characters after number. Try again.\n");
continue;
}
break;
}
}
static void _get_log_interpolated_airflow_pressure_drop_limit(double *limit, const double *airflow, const air_passage_data_t *data)
{
if (*airflow <= data->air_passage_anchors[0].airflow)
{
*limit = data->air_passage_anchors[0].pressure;
return;
}
if (*airflow >= data->air_passage_anchors[data->anchors_data_size - 1].airflow)
{
*limit = data->air_passage_anchors[data->anchors_data_size - 1].pressure;
return;
}
for (uint8_t i = 0; i < data->anchors_data_size - 1; ++i)
{
if (*airflow >= data->air_passage_anchors[i].airflow &&
*airflow <= data->air_passage_anchors[i + 1].airflow)
{
double log_x = log(*airflow);
double log_x1 = log(data->air_passage_anchors[i].airflow);
double log_x2 = log(data->air_passage_anchors[i + 1].airflow);
double log_y1 = log(data->air_passage_anchors[i].pressure);
double log_y2 = log(data->air_passage_anchors[i + 1].pressure);
double log_y = log_y1 + (log_x - log_x1) * (log_y2 - log_y1) / (log_x2 - log_x1);
*limit = exp(log_y);
return;
}
}
*limit = -1.0;
}
static void _calculate_density_corrected_pressure(double *corrected_airflow_pressure, const double *airflow_pressure, const double *barometric_pressure, const double *airflow_temperature)
{
double value_4 = *airflow_pressure * 0.5;
double value_5 = *barometric_pressure + value_4;
double value_6 = *airflow_temperature + 273.2;
double value_7 = (0.28443 * value_5) / value_6;
*corrected_airflow_pressure = *airflow_pressure * value_7;
}
+207
View File
@@ -0,0 +1,207 @@
#pragma once
/**
* @file ats0002.h
*
* @brief Heat exchanger data management and user input handling for ATS0002.
*
* This module provides data structures and functions for managing heat
* exchanger configuration data including air passage descriptions, pressure
* to airflow anchor points, and user interaction functions for selecting
* heat exchanger models and retrieving operational parameters.
*
* Key features:
* - Hierarchical data structures for heat exchanger, type, and air passage configuration
* - User input functions for command selection and parameter retrieval
* - Pressure drop calculation support with temperature and barometric compensation
*
* @note All data structures use const pointers to read-only configuration data.
*/
#include "stdint.h"
#include "stdio.h"
#include "stdbool.h"
#include "stdlib.h"
#include "string.h"
#include "math.h"
#include "locale.h"
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
#define EXIT 0xFFFF
#define REPEAT 0xFFEE
#define EXIT_CHECK(cond) \
if (cond == EXIT) \
{ \
printf("Goodbye.\n"); \
return 0; \
}
#define ASK_CHOICE(var, getter, count) \
(var) = 0; \
getter(&(var), (count)); \
EXIT_CHECK(var);
typedef enum
{
KPA,
PSIG
} airflow_pressure_unit_t;
/**
* @brief Airflow to pressure anchor point data structure.
*
* Represents a pair of airflow and corresponding pressure values used as
* interpolation anchor points for heat exchanger performance curves.
*/
typedef struct
{
double airflow; /*!< Airflow rate value (LB/MIN) */
double pressure; /*!< Corresponding pressure drop value (KPA or PSIG) */
} airflow_to_pressure_anchors_data_t;
/**
* @brief Air passage configuration data structure.
*
* Contains description, pressure-to-airflow anchor points, and operational
* parameter limits for a single air passage within a heat exchanger.
*/
typedef struct
{
const char *air_passage_description; /*!< Human-readable description of the air passage */
const airflow_to_pressure_anchors_data_t *air_passage_anchors; /*!< Pointer to anchor point array */
const uint8_t anchors_data_size; /*!< Number of anchor points in the array */
const bool recalculated_required; /*!< Flag indicating if recalculation is required */
const airflow_pressure_unit_t airflow_pressure_unit;
const double min_barometric_pressure; /*!< Minimum allowed barometric pressure (Pa) */
const double max_barometric_pressure; /*!< Maximum allowed barometric pressure (Pa) */
const double min_airflow_temperature; /*!< Minimum allowed air temperature (C) */
const double max_airflow_temperature; /*!< Maximum allowed air temperature (C) */
const double min_airflow_pressure; /*!< Minimum allowed air pressure (KPA or PSIG) */
const double max_airflow_pressure; /*!< Maximum allowed air pressure (KPA or PSIG) */
} air_passage_data_t;
/**
* @brief Heat exchanger type configuration data structure.
*
* Contains description and an array of air passage configurations for a
* specific heat exchanger part number.
*/
typedef struct
{
const char *part_number_description; /*!< Human-readable part number description */
const air_passage_data_t *air_passages_data; /*!< Pointer to air passage configuration array */
const uint8_t air_passages_data_size; /*!< Number of air passages in the array */
} heat_exchanger_type_data_t;
/**
* @brief Heat exchanger configuration data structure.
*
* Contains description and an array of heat exchanger type configurations
* representing all supported part numbers for a given heat exchanger model.
*/
typedef struct
{
const char *heat_exchanger_description; /*!< Human-readable heat exchanger model description */
const heat_exchanger_type_data_t *heat_exchanger_type_data; /*!< Pointer to heat exchanger type array */
const uint8_t heat_exchanger_type_data_size; /*!< Number of heat exchanger types in the array */
} heat_exchanger_data_t;
/**
* @brief Retrieve a user command from standard input.
*
* Reads a command selection from the user. The user can select a numeric
* command or enter EXIT to terminate the program.
*
* @param[out] user_command Pointer to store the selected command value
*/
void get_user_command(uint16_t *user_command);
/**
* @brief Retrieve a heat exchanger model number from user input.
*
* Prompts the user to select a heat exchanger model from the available options.
*
* @param[out] heat_exchanger_number Pointer to store the selected model number
* @param[in] structure_size Number of available heat exchanger models
*/
void get_heat_exchanger_number(uint16_t *heat_exchanger_number, uint8_t structure_size);
/**
* @brief Retrieve a heat exchanger part number from user input.
*
* Prompts the user to select a part number for the selected heat exchanger model.
*
* @param[out] heat_exchanger_part_number Pointer to store the selected part number
* @param[in] structure_size Number of available part numbers
*/
void get_heat_exchanger_part_number(uint16_t *heat_exchanger_part_number, uint8_t structure_size);
/**
* @brief Retrieve an air passage number from user input.
*
* Prompts the user to select an air passage for the selected heat exchanger part.
*
* @param[out] heat_exchanger_air_passage_number Pointer to store the selected passage number
* @param[in] structure_size Number of available air passages
*/
void get_heat_exchanger_air_passage_number(uint16_t *heat_exchanger_air_passage_number, uint8_t structure_size);
/**
* @brief Retrieve an airflow value from user input.
*
* Validates the entered airflow against the operational limits defined in the
* provided air passage data.
*
* @param[out] airflow Pointer to store the entered airflow value
* @param[in] data Pointer to air passage data containing operational limits (must not be NULL)
*/
void get_airflow(double *airflow, const air_passage_data_t *data);
/**
* @brief Retrieve an airflow pressure value from user input.
*
* Validates the entered pressure against the operational limits defined in the
* provided air passage data.
*
* @param[out] airflow_pressure Pointer to store the entered pressure value
* @param[in] data Pointer to air passage data containing operational limits (must not be NULL)
*/
void get_airflow_pressure(double *airflow_pressure, const air_passage_data_t *data);
/**
* @brief Retrieve a barometric pressure value from user input.
*
* Validates the entered barometric pressure against the operational limits
* defined in the provided air passage data.
*
* @param[out] barometric_pressure Pointer to store the entered pressure value
* @param[in] data Pointer to air passage data containing operational limits (must not be NULL)
*/
void get_barometric_pressure(double *barometric_pressure, const air_passage_data_t *data);
/**
* @brief Retrieve an airflow temperature value from user input.
*
* Validates the entered temperature against the operational limits defined in
* the provided air passage data.
*
* @param[out] airflow_temperature Pointer to store the entered temperature value
* @param[in] data Pointer to air passage data containing operational limits (must not be NULL)
*/
void get_airflow_temperature(double *airflow_temperature, const air_passage_data_t *data);
/**
* @brief Retrieve a pressure drop limit value from user input.
*
* Reads a pressure drop limit from the user and validates it against the
* operational limits defined in the provided air passage data.
*
* @param[out] pressure_drop_limit Pointer to store the entered pressure drop limit
* @param[in] airflow Pointer to store the current airflow value
* @param[in] barometric_pressure Pointer to store the current barometric pressure value
* @param[in] airflow_temperature Pointer to store the current airflow temperature value
* @param[in] data Pointer to air passage data containing operational limits (must not be NULL)
*/
void get_pressure_drop(double *pressure_drop_limit, const double *airflow, const double *airflow_pressure, double *corrected_airflow_pressure, const double *barometric_pressure, const double *airflow_temperature, const air_passage_data_t *data);
Binary file not shown.
+103
View File
@@ -0,0 +1,103 @@
#include "ats0002/ats0002.h"
#include "ats0002/21_53_89.h"
#include "ats0002/21_53_88.h"
#include "ats0002/21_50_14.h"
static const heat_exchanger_data_t heat_exchanger_data[] = {
{"Honeywell Heat Exchanger CMM 21-50-14", heat_exchanger_21_50_14_data, ARRAY_SIZE(heat_exchanger_21_50_14_data)},
{"Liebherr Reheater CMM 21-53-89", heat_exchanger_21_53_89_data, ARRAY_SIZE(heat_exchanger_21_53_89_data)},
{"Liebherr Condenser CMM 21-53-88", heat_exchanger_21_53_88_data, ARRAY_SIZE(heat_exchanger_21_53_88_data)}};
int main(void)
{
setlocale(LC_NUMERIC, "C");
for (;;)
{
#ifdef _WIN32
system("cls");
#else
system("clear");
#endif
printf("************************************************************\n"
"* *\n"
"* Heat Exchanger Airflow Pressure Drop Calculation Program *\n"
"* *\n"
"* Developed by @rick_professor *\n"
"* Participation by @cabanleo *\n"
"* Property of Aerotech LLC *\n"
"* *\n"
"************************************************************\n"
"\n");
for (uint8_t i = 0; i < ARRAY_SIZE(heat_exchanger_data); ++i)
{
printf("%d. %s\n", i + 1, heat_exchanger_data[i].heat_exchanger_description);
}
printf("\n");
uint16_t heat_exchanger_number = 0;
ASK_CHOICE(heat_exchanger_number, get_heat_exchanger_number, ARRAY_SIZE(heat_exchanger_data));
printf("\n");
const heat_exchanger_data_t *he = &heat_exchanger_data[heat_exchanger_number];
for (uint8_t i = 0; i < he->heat_exchanger_type_data_size; ++i)
{
printf("%d. %s\n", i + 1, he->heat_exchanger_type_data[i].part_number_description);
}
printf("\n");
uint16_t part_number = 0;
ASK_CHOICE(part_number, get_heat_exchanger_part_number, he->heat_exchanger_type_data_size);
printf("\n");
const heat_exchanger_type_data_t *hetd = &he->heat_exchanger_type_data[part_number];
for (uint8_t i = 0; i < hetd->air_passages_data_size; ++i)
{
printf("%d. %s\n", i + 1, hetd->air_passages_data[i].air_passage_description);
}
printf("\n");
uint16_t air_passage_number = 0;
ASK_CHOICE(air_passage_number, get_heat_exchanger_air_passage_number, hetd->air_passages_data_size);
printf("\n");
const air_passage_data_t *apd = &hetd->air_passages_data[air_passage_number];
double airflow = 0.0;
get_airflow(&airflow, apd);
EXIT_CHECK(airflow);
printf("\n");
double airflow_pressure = 0.0;
get_airflow_pressure(&airflow_pressure, apd);
EXIT_CHECK(airflow_pressure);
printf("\n");
double barometric_pressure = 0.0;
double airflow_temperature = 0.0;
if (apd->recalculated_required == true)
{
get_barometric_pressure(&barometric_pressure, apd);
EXIT_CHECK(barometric_pressure);
printf("\n");
get_airflow_temperature(&airflow_temperature, apd);
EXIT_CHECK(airflow_temperature);
printf("\n");
}
double pressure_drop_limit = 0.0;
double corrected_airflow_pressure = 0.0;
get_pressure_drop(&pressure_drop_limit, &airflow, &airflow_pressure, &corrected_airflow_pressure, &barometric_pressure, &airflow_temperature, apd);
const char *unit = apd->airflow_pressure_unit == KPA ? "KPA" : "PSIG";
printf("****************** Test summary *******************\n");
printf("Name: %s\n", he->heat_exchanger_description);
printf("P/N: %s\n", he->heat_exchanger_type_data[part_number].part_number_description);
printf("Side: %s\n", hetd->air_passages_data[air_passage_number].air_passage_description);
printf("Airflow: %0.2f LB/MIN\n", airflow);
printf("Static inlet pressure: %0.2f %s\n", airflow_pressure, unit);
if (apd->recalculated_required == true)
{
printf("Barometric pressure: %0.2f MBAR\n", barometric_pressure);
printf("Airflow temperature: %0.2f C\n", airflow_temperature);
printf("Corrected static inlet pressure: %0.2f %s\n", corrected_airflow_pressure, unit);
}
printf("Max allowed pressure drop: %0.2f %s\n", pressure_drop_limit, unit);
double test_pressure = apd->recalculated_required == true ? corrected_airflow_pressure : airflow_pressure;
printf("Test result: %s\n", test_pressure <= pressure_drop_limit ? "PASSED" : "FAILED");
printf("***************************************************\n");
printf("\n");
uint16_t command = 0;
get_user_command(&command);
EXIT_CHECK(command);
}
return 0;
}
Binary file not shown.
View File
Whitespace-only changes.
View File
Whitespace-only changes.
+1
View File
@@ -0,0 +1 @@
1.0.0