Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c79271056 |
No files matched your search
+1
-3
@@ -1,5 +1,3 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
heat_exchander_airflow_pressure_drop.dSYM
|
heat_exchander_airflow_pressure_drop.dSYM
|
||||||
commit.md
|
commit.md
|
||||||
commit-rules.md
|
|
||||||
wiki-rules.md
|
|
||||||
@@ -3,11 +3,8 @@
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
1. Calculation of pressure drop across heat exchangers.
|
1. Calculation of pressure drop across heat exchangers.
|
||||||
2. Support for heat exchanger models:
|
2. Support for three heat exchanger models:
|
||||||
- Honeywell Heat Exchanger CMM 21-50-14
|
- Honeywell Heat Exchanger CMM 21-50-14
|
||||||
- Honeywell Ram Heat Exchanger CMM 21-52-80
|
|
||||||
- Honeywell NGS Heat Exchanger CMM 47-32-06
|
|
||||||
- Honeywell NGS Regen Heat Exchanger CMM 47-32-14
|
|
||||||
- Liebherr Reheater CMM 21-53-89
|
- Liebherr Reheater CMM 21-53-89
|
||||||
- Liebherr Condenser CMM 21-53-88
|
- Liebherr Condenser CMM 21-53-88
|
||||||
3. Multiple part number selection for each heat exchanger.
|
3. Multiple part number selection for each heat exchanger.
|
||||||
|
|||||||
@@ -1,217 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "ats0002.h"
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C"
|
|
||||||
{
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @file 21_52_80.h
|
|
||||||
*
|
|
||||||
* @brief Honeywell Ram Heat Exchanger CMM 21-52-80 configuration data.
|
|
||||||
*
|
|
||||||
* This module provides static configuration data for Honeywell Ram Heat
|
|
||||||
* Exchanger CMM 21-52-80, including pressure-to-airflow anchor points for
|
|
||||||
* cooling and hot sides, air passage definitions, and heat exchanger type
|
|
||||||
* mappings for part numbers 67200152-00 and 67200152-01.
|
|
||||||
*
|
|
||||||
* Key features:
|
|
||||||
* - Cooling side performance data (Figure TEST-8)
|
|
||||||
* - Primary hot side performance data (Figure TEST-10)
|
|
||||||
* - Main hot side performance data (Figure TEST-12)
|
|
||||||
* - Complete air passage configuration with operational limits
|
|
||||||
*
|
|
||||||
* @note All data arrays are static and const-qualified for read-only access.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Honeywell Ram Heat Exchanger CMM 21-52-80, Figure TEST-8 (P/N 67200152-00).
|
|
||||||
*
|
|
||||||
* Cooling side dependence of ΔPS (KPA) on air flow rate (LB/MIN), UPPER LIMIT.
|
|
||||||
*/
|
|
||||||
static const airflow_to_pressure_anchors_data_t _cooling_side_67200152_00[] = {
|
|
||||||
{50.0, 0.06},
|
|
||||||
{60.0, 0.08},
|
|
||||||
{70.0, 0.10},
|
|
||||||
{80.0, 0.13},
|
|
||||||
{90.0, 0.15},
|
|
||||||
{100.0, 0.17},
|
|
||||||
{200.0, 0.47},
|
|
||||||
{300.0, 0.85},
|
|
||||||
{400.0, 1.30},
|
|
||||||
{500.0, 1.74},
|
|
||||||
{600.0, 2.36}};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Honeywell Ram Heat Exchanger CMM 21-52-80, Figure TEST-8 (P/N 67200152-01).
|
|
||||||
*
|
|
||||||
* Cooling side dependence of ΔPS (KPA) on air flow rate (LB/MIN), UPPER LIMIT.
|
|
||||||
*/
|
|
||||||
static const airflow_to_pressure_anchors_data_t _cooling_side_67200152_01[] = {
|
|
||||||
{50.0, 0.06},
|
|
||||||
{60.0, 0.08},
|
|
||||||
{70.0, 0.10},
|
|
||||||
{80.0, 0.13},
|
|
||||||
{90.0, 0.15},
|
|
||||||
{100.0, 0.17},
|
|
||||||
{200.0, 0.47},
|
|
||||||
{300.0, 0.85},
|
|
||||||
{400.0, 1.30},
|
|
||||||
{500.0, 1.74},
|
|
||||||
{600.0, 2.36}};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Honeywell Ram Heat Exchanger CMM 21-52-80, Figure TEST-10 (P/N 67200152-00).
|
|
||||||
*
|
|
||||||
* Primary hot side dependence of ΔPS (PSIG) on air flow rate (LB/MIN), UPPER LIMIT.
|
|
||||||
*/
|
|
||||||
static const airflow_to_pressure_anchors_data_t _primary_hot_side_67200152_00[] = {
|
|
||||||
{25.0, 0.14},
|
|
||||||
{30.0, 0.19},
|
|
||||||
{40.0, 0.29},
|
|
||||||
{50.0, 0.42},
|
|
||||||
{60.0, 0.61},
|
|
||||||
{70.0, 0.80},
|
|
||||||
{80.0, 0.92},
|
|
||||||
{90.0, 1.14},
|
|
||||||
{100.0, 1.38},
|
|
||||||
{200.0, 4.91},
|
|
||||||
{300.0, 11.30}};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Honeywell Ram Heat Exchanger CMM 21-52-80, Figure TEST-10 (P/N 67200152-01).
|
|
||||||
*
|
|
||||||
* Primary hot side dependence of ΔPS (PSIG) on air flow rate (LB/MIN), UPPER LIMIT.
|
|
||||||
*/
|
|
||||||
static const airflow_to_pressure_anchors_data_t _primary_hot_side_67200152_01[] = {
|
|
||||||
{25.0, 0.14},
|
|
||||||
{30.0, 0.19},
|
|
||||||
{40.0, 0.29},
|
|
||||||
{50.0, 0.42},
|
|
||||||
{60.0, 0.61},
|
|
||||||
{70.0, 0.80},
|
|
||||||
{80.0, 0.92},
|
|
||||||
{90.0, 1.14},
|
|
||||||
{100.0, 1.38},
|
|
||||||
{200.0, 4.91},
|
|
||||||
{300.0, 11.30}};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Honeywell Ram Heat Exchanger CMM 21-52-80, Figure TEST-12 (P/N 67200152-00).
|
|
||||||
*
|
|
||||||
* Main hot side dependence of ΔPS (PSIG) on air flow rate (LB/MIN), UPPER LIMIT.
|
|
||||||
*/
|
|
||||||
static const airflow_to_pressure_anchors_data_t _main_hot_side_67200152_00[] = {
|
|
||||||
{25.0, 0.34},
|
|
||||||
{30.0, 0.43},
|
|
||||||
{40.0, 0.68},
|
|
||||||
{50.0, 0.89},
|
|
||||||
{60.0, 1.18},
|
|
||||||
{70.0, 1.47},
|
|
||||||
{80.0, 1.82},
|
|
||||||
{90.0, 2.26},
|
|
||||||
{100.0, 2.75},
|
|
||||||
{200.0, 9.82},
|
|
||||||
{300.0, 27.01}};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Honeywell Ram Heat Exchanger CMM 21-52-80, Figure TEST-12 (P/N 67200152-01).
|
|
||||||
*
|
|
||||||
* Main hot side dependence of ΔPS (PSIG) on air flow rate (LB/MIN), UPPER LIMIT.
|
|
||||||
*/
|
|
||||||
static const airflow_to_pressure_anchors_data_t _main_hot_side_67200152_01[] = {
|
|
||||||
{25.0, 0.34},
|
|
||||||
{30.0, 0.43},
|
|
||||||
{40.0, 0.68},
|
|
||||||
{50.0, 0.89},
|
|
||||||
{60.0, 1.18},
|
|
||||||
{70.0, 1.47},
|
|
||||||
{80.0, 1.82},
|
|
||||||
{90.0, 2.26},
|
|
||||||
{100.0, 2.75},
|
|
||||||
{200.0, 9.82},
|
|
||||||
{300.0, 27.01}};
|
|
||||||
|
|
||||||
static const air_passage_data_t _air_passage_67200152_00_data[] = {
|
|
||||||
{.air_passage_description = "Cooling Air Side",
|
|
||||||
.air_passage_anchors = _cooling_side_67200152_00,
|
|
||||||
.anchors_data_size = ARRAY_SIZE(_cooling_side_67200152_00),
|
|
||||||
.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 = "Primary Bleed Air Side",
|
|
||||||
.air_passage_anchors = _primary_hot_side_67200152_00,
|
|
||||||
.anchors_data_size = ARRAY_SIZE(_primary_hot_side_67200152_00),
|
|
||||||
.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},
|
|
||||||
{.air_passage_description = "Main Bleed Air Side",
|
|
||||||
.air_passage_anchors = _main_hot_side_67200152_00,
|
|
||||||
.anchors_data_size = ARRAY_SIZE(_main_hot_side_67200152_00),
|
|
||||||
.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_67200152_01_data[] = {
|
|
||||||
{.air_passage_description = "Cooling Air Side",
|
|
||||||
.air_passage_anchors = _cooling_side_67200152_01,
|
|
||||||
.anchors_data_size = ARRAY_SIZE(_cooling_side_67200152_01),
|
|
||||||
.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 = "Primary Bleed Air Side",
|
|
||||||
.air_passage_anchors = _primary_hot_side_67200152_01,
|
|
||||||
.anchors_data_size = ARRAY_SIZE(_primary_hot_side_67200152_01),
|
|
||||||
.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},
|
|
||||||
{.air_passage_description = "Main Bleed Air Side",
|
|
||||||
.air_passage_anchors = _main_hot_side_67200152_01,
|
|
||||||
.anchors_data_size = ARRAY_SIZE(_main_hot_side_67200152_01),
|
|
||||||
.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_52_80_data[] = {
|
|
||||||
{.part_number_description = "67200152-00",
|
|
||||||
.air_passages_data = _air_passage_67200152_00_data,
|
|
||||||
.air_passages_data_size = ARRAY_SIZE(_air_passage_67200152_00_data)},
|
|
||||||
{.part_number_description = "67200152-01",
|
|
||||||
.air_passages_data = _air_passage_67200152_01_data,
|
|
||||||
.air_passages_data_size = ARRAY_SIZE(_air_passage_67200152_01_data)}};
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "ats0002.h"
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C"
|
|
||||||
{
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @file 47_32_06.h
|
|
||||||
*
|
|
||||||
* @brief Honeywell NGS Heat Exchanger CMM 47-32-06 configuration data.
|
|
||||||
*
|
|
||||||
* This module provides static configuration data for Honeywell NGS Heat
|
|
||||||
* Exchanger CMM 47-32-06, including pressure-to-airflow anchor points for
|
|
||||||
* cooling and hot sides, air passage definitions, and heat exchanger type
|
|
||||||
* mappings for part number 2341790-1.
|
|
||||||
*
|
|
||||||
* Key features:
|
|
||||||
* - Cooling side performance data (Figure 4002)
|
|
||||||
* - Hot side performance data (Figure 4004)
|
|
||||||
* - Complete air passage configuration with operational limits
|
|
||||||
*
|
|
||||||
* @note All data arrays are static and const-qualified for read-only access.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Honeywell NGS Heat Exchanger CMM 47-32-06, Figure 4002 (P/N 2341790-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_2341790_1[] = {
|
|
||||||
{2.6, 0.02},
|
|
||||||
{3.0, 0.03},
|
|
||||||
{4.0, 0.04},
|
|
||||||
{5.0, 0.06},
|
|
||||||
{6.0, 0.07},
|
|
||||||
{7.0, 0.09},
|
|
||||||
{8.0, 0.11},
|
|
||||||
{9.0, 0.14},
|
|
||||||
{10.0, 0.17},
|
|
||||||
{20.0, 0.55},
|
|
||||||
{30.0, 1.12},
|
|
||||||
{40.0, 1.84}};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Honeywell NGS Heat Exchanger CMM 47-32-06, Figure 4004 (P/N 2341790-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_2341790_1[] = {
|
|
||||||
{1.6, 0.04},
|
|
||||||
{2.0, 0.05},
|
|
||||||
{3.0, 0.07},
|
|
||||||
{4.0, 0.10},
|
|
||||||
{5.0, 0.14},
|
|
||||||
{6.0, 0.18},
|
|
||||||
{7.0, 0.24},
|
|
||||||
{8.0, 0.29},
|
|
||||||
{9.0, 0.35},
|
|
||||||
{10.0, 0.42},
|
|
||||||
{20.0, 1.37},
|
|
||||||
{30.0, 2.89},
|
|
||||||
{35.0, 3.61}};
|
|
||||||
|
|
||||||
static const air_passage_data_t _air_passage_2341790_1_data[] = {
|
|
||||||
{.air_passage_description = "Ram Air Side",
|
|
||||||
.air_passage_anchors = _cooling_side_2341790_1,
|
|
||||||
.anchors_data_size = ARRAY_SIZE(_cooling_side_2341790_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_2341790_1,
|
|
||||||
.anchors_data_size = ARRAY_SIZE(_hot_side_2341790_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 heat_exchanger_type_data_t heat_exchanger_47_32_06_data[] = {
|
|
||||||
{.part_number_description = "2341790-1",
|
|
||||||
.air_passages_data = _air_passage_2341790_1_data,
|
|
||||||
.air_passages_data_size = ARRAY_SIZE(_air_passage_2341790_1_data)}};
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "ats0002.h"
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C"
|
|
||||||
{
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @file 47_32_14.h
|
|
||||||
*
|
|
||||||
* @brief Honeywell NGS Regen Heat Exchanger CMM 47-32-14 configuration data.
|
|
||||||
*
|
|
||||||
* This module provides static configuration data for Honeywell NGS Regen Heat
|
|
||||||
* Exchanger CMM 47-32-14, including pressure-to-airflow anchor points for
|
|
||||||
* cooling and hot sides, air passage definitions, and heat exchanger type
|
|
||||||
* mappings for part number 2342176-1.
|
|
||||||
*
|
|
||||||
* Key features:
|
|
||||||
* - Cooling side performance data (Figure 1005)
|
|
||||||
* - Hot side performance data (Figure 1008)
|
|
||||||
* - Complete air passage configuration with operational limits
|
|
||||||
*
|
|
||||||
* @note All data arrays are static and const-qualified for read-only access.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Honeywell NGS Regen Heat Exchanger CMM 47-32-14, Figure 1005 (P/N 2342176-1).
|
|
||||||
*
|
|
||||||
* Cooling side dependence of ΔPS (PSIG) on air flow rate (LB/MIN), UPPER LIMIT.
|
|
||||||
*/
|
|
||||||
static const airflow_to_pressure_anchors_data_t _cooling_side_2342176_1[] = {
|
|
||||||
{3.0, 0.10},
|
|
||||||
{4.0, 0.17},
|
|
||||||
{5.0, 0.23},
|
|
||||||
{6.0, 0.32},
|
|
||||||
{7.0, 0.41},
|
|
||||||
{8.0, 0.52},
|
|
||||||
{9.0, 0.63},
|
|
||||||
{10.0, 0.79}};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Honeywell NGS Regen Heat Exchanger CMM 47-32-14, Figure 1008 (P/N 2342176-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_2342176_1[] = {
|
|
||||||
{3.0, 0.13},
|
|
||||||
{4.0, 0.21},
|
|
||||||
{5.0, 0.30},
|
|
||||||
{6.0, 0.40},
|
|
||||||
{7.0, 0.52},
|
|
||||||
{8.0, 0.66},
|
|
||||||
{9.0, 0.81},
|
|
||||||
{10.0, 1.01}};
|
|
||||||
|
|
||||||
static const air_passage_data_t _air_passage_2342176_1_data[] = {
|
|
||||||
{.air_passage_description = "Cold Air Side",
|
|
||||||
.air_passage_anchors = _cooling_side_2342176_1,
|
|
||||||
.anchors_data_size = ARRAY_SIZE(_cooling_side_2342176_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},
|
|
||||||
{.air_passage_description = "Hot Air Side",
|
|
||||||
.air_passage_anchors = _hot_side_2342176_1,
|
|
||||||
.anchors_data_size = ARRAY_SIZE(_hot_side_2342176_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 heat_exchanger_type_data_t heat_exchanger_47_32_14_data[] = {
|
|
||||||
{.part_number_description = "2342176-1",
|
|
||||||
.air_passages_data = _air_passage_2342176_1_data,
|
|
||||||
.air_passages_data_size = ARRAY_SIZE(_air_passage_2342176_1_data)}};
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
+266
-2
@@ -1,4 +1,6 @@
|
|||||||
#include "ats0002.h"
|
#include "ats0002.h"
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Normalize the decimal separator in a string from comma to dot.
|
* @brief Normalize the decimal separator in a string from comma to dot.
|
||||||
@@ -60,6 +62,31 @@ static void _get_double_in_range(double *result, double min, double max, const c
|
|||||||
* @param airflow Pointer to the current airflow value
|
* @param airflow Pointer to the current airflow value
|
||||||
* @param data Pointer to air passage data containing anchor points (must not be NULL)
|
* @param data Pointer to air passage data containing anchor points (must not be NULL)
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* @brief Get the current date as a formatted string (YYYY-MM-DD).
|
||||||
|
*
|
||||||
|
* @param buffer Destination buffer (at least 11 bytes)
|
||||||
|
*/
|
||||||
|
static void _get_date_string(char *buffer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Save the test report to a minimal PDF file.
|
||||||
|
*
|
||||||
|
* @see save_report_to_pdf (public API)
|
||||||
|
*/
|
||||||
|
static void _save_report_to_pdf_impl(const char *heat_exchanger_name,
|
||||||
|
const char *part_number,
|
||||||
|
const char *side_name,
|
||||||
|
double airflow,
|
||||||
|
double airflow_pressure,
|
||||||
|
const char *unit,
|
||||||
|
const double *barometric_pressure,
|
||||||
|
const double *airflow_temperature,
|
||||||
|
const double *corrected_airflow_pressure,
|
||||||
|
double pressure_drop_limit,
|
||||||
|
const char *test_result,
|
||||||
|
bool recalculated_required);
|
||||||
|
|
||||||
static void _get_log_interpolated_airflow_pressure_drop_limit(double *limit, const double *airflow, const air_passage_data_t *data);
|
static void _get_log_interpolated_airflow_pressure_drop_limit(double *limit, const double *airflow, const air_passage_data_t *data);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -104,6 +131,7 @@ static void _get_selection(uint16_t *result, uint8_t size, const char *prompt, c
|
|||||||
{
|
{
|
||||||
double input = 0.0;
|
double input = 0.0;
|
||||||
_get_user_input(&input);
|
_get_user_input(&input);
|
||||||
|
|
||||||
if (input == (double)EXIT)
|
if (input == (double)EXIT)
|
||||||
{
|
{
|
||||||
*result = EXIT;
|
*result = EXIT;
|
||||||
@@ -189,7 +217,7 @@ void get_airflow_temperature(double *airflow_temperature, const air_passage_data
|
|||||||
{
|
{
|
||||||
double min = data->min_airflow_temperature;
|
double min = data->min_airflow_temperature;
|
||||||
double max = data->max_airflow_temperature;
|
double max = data->max_airflow_temperature;
|
||||||
_get_double_in_range(airflow_temperature, min, max, "Enter airflow temperature in C", "Incorrect airflow temperature. Try again.");
|
_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)
|
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)
|
||||||
@@ -306,7 +334,8 @@ static void _get_log_interpolated_airflow_pressure_drop_limit(double *limit, con
|
|||||||
}
|
}
|
||||||
for (uint8_t i = 0; i < data->anchors_data_size - 1; ++i)
|
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)
|
if (*airflow >= data->air_passage_anchors[i].airflow &&
|
||||||
|
*airflow <= data->air_passage_anchors[i + 1].airflow)
|
||||||
{
|
{
|
||||||
double log_x = log(*airflow);
|
double log_x = log(*airflow);
|
||||||
double log_x1 = log(data->air_passage_anchors[i].airflow);
|
double log_x1 = log(data->air_passage_anchors[i].airflow);
|
||||||
@@ -328,4 +357,239 @@ static void _calculate_density_corrected_pressure(double *corrected_airflow_pres
|
|||||||
double value_6 = *airflow_temperature + 273.2;
|
double value_6 = *airflow_temperature + 273.2;
|
||||||
double value_7 = (0.28443 * value_5) / value_6;
|
double value_7 = (0.28443 * value_5) / value_6;
|
||||||
*corrected_airflow_pressure = *airflow_pressure * value_7;
|
*corrected_airflow_pressure = *airflow_pressure * value_7;
|
||||||
|
}
|
||||||
|
|
||||||
|
void get_yes_no(bool *answer, const char *prompt)
|
||||||
|
{
|
||||||
|
printf("%s (y/n):\n", prompt);
|
||||||
|
char buf[64];
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
printf("> ");
|
||||||
|
fflush(stdout);
|
||||||
|
if (fgets(buf, sizeof(buf), stdin) == NULL)
|
||||||
|
{
|
||||||
|
*answer = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
buf[strcspn(buf, "\n")] = '\0';
|
||||||
|
if (strcmp(buf, "q") == 0 || strcmp(buf, "Q") == 0)
|
||||||
|
{
|
||||||
|
*answer = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (strcmp(buf, "y") == 0 || strcmp(buf, "Y") == 0 ||
|
||||||
|
strcmp(buf, "yes") == 0 || strcmp(buf, "Yes") == 0 || strcmp(buf, "YES") == 0)
|
||||||
|
{
|
||||||
|
*answer = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (strcmp(buf, "n") == 0 || strcmp(buf, "N") == 0 ||
|
||||||
|
strcmp(buf, "no") == 0 || strcmp(buf, "No") == 0 || strcmp(buf, "NO") == 0)
|
||||||
|
{
|
||||||
|
*answer = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
printf("Please enter 'y' or 'n':\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ======================== PDF Report Generation ======================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Escape special characters in PDF string literals.
|
||||||
|
*
|
||||||
|
* PDF strings require backslash-escaping for: \ ( )
|
||||||
|
*
|
||||||
|
* @param[out] dst Destination buffer
|
||||||
|
* @param[in] dst_size Size of destination buffer
|
||||||
|
* @param[in] src Source string
|
||||||
|
*/
|
||||||
|
static void _pdf_escape_string(char *dst, size_t dst_size, const char *src)
|
||||||
|
{
|
||||||
|
size_t di = 0;
|
||||||
|
for (size_t si = 0; src[si] != '\0' && di + 2 < dst_size; si++)
|
||||||
|
{
|
||||||
|
char c = src[si];
|
||||||
|
if (c == 0x5C || c == 0x28 || c == 0x29)
|
||||||
|
{
|
||||||
|
dst[di++] = 0x5C;
|
||||||
|
}
|
||||||
|
dst[di++] = c;
|
||||||
|
}
|
||||||
|
dst[di] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the current date as a formatted string (YYYY-MM-DD).
|
||||||
|
*
|
||||||
|
* @param buffer Destination buffer (at least 11 bytes)
|
||||||
|
*/
|
||||||
|
static void _get_date_string(char *buffer)
|
||||||
|
{
|
||||||
|
time_t now = time(NULL);
|
||||||
|
struct tm *t = localtime(&now);
|
||||||
|
snprintf(buffer, 11, "%04d-%02d-%02d", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Build a PDF content stream line for a text label.
|
||||||
|
*
|
||||||
|
* Appends a BT/ET text block to the stream buffer and advances the y position.
|
||||||
|
*
|
||||||
|
* @param stream_buf Content stream buffer
|
||||||
|
* @param stream_size Size of the content stream buffer
|
||||||
|
* @param slen Current stream length (updated in-place)
|
||||||
|
* @param y Current y position (updated in-place: decremented by 18)
|
||||||
|
* @param font_size Font size in points
|
||||||
|
* @param fmt printf-style format string for the text
|
||||||
|
*/
|
||||||
|
static void _pdf_add_text_line(char *stream_buf, size_t stream_size, int *slen, double *y,
|
||||||
|
int font_size, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
char raw_line[512];
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
vsnprintf(raw_line, sizeof(raw_line), fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
char escaped[1024];
|
||||||
|
_pdf_escape_string(escaped, sizeof(escaped), raw_line);
|
||||||
|
|
||||||
|
*slen += snprintf(stream_buf + *slen, stream_size - (size_t)*slen,
|
||||||
|
"BT\n/F1 %d Tf\n50 %.0f Td\n(%s) Tj\nET\n",
|
||||||
|
font_size, *y, escaped);
|
||||||
|
*y -= 18;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimal PDF generator. Creates a single-page PDF with text content.
|
||||||
|
* Uses PDF 1.4 format with Type1 font (Helvetica). No external dependencies.
|
||||||
|
*/
|
||||||
|
static void _save_report_to_pdf_impl(const char *heat_exchanger_name,
|
||||||
|
const char *part_number,
|
||||||
|
const char *side_name,
|
||||||
|
double airflow,
|
||||||
|
double airflow_pressure,
|
||||||
|
const char *unit,
|
||||||
|
const double *barometric_pressure,
|
||||||
|
const double *airflow_temperature,
|
||||||
|
const double *corrected_airflow_pressure,
|
||||||
|
double pressure_drop_limit,
|
||||||
|
const char *test_result,
|
||||||
|
bool recalculated_required)
|
||||||
|
{
|
||||||
|
char date_str[11];
|
||||||
|
_get_date_string(date_str);
|
||||||
|
|
||||||
|
/* Build filename: <P/N>_<YYYY-MM-DD>.pdf in project root */
|
||||||
|
char filename[256];
|
||||||
|
snprintf(filename, sizeof(filename), "%s_%s.pdf", part_number, date_str);
|
||||||
|
|
||||||
|
FILE *f = fopen(filename, "wb");
|
||||||
|
if (!f)
|
||||||
|
{
|
||||||
|
printf("Error: Cannot create PDF file '%s'\n", filename);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Build content stream in memory --- */
|
||||||
|
char stream_buf[4096];
|
||||||
|
int slen = 0;
|
||||||
|
double y = 750;
|
||||||
|
|
||||||
|
/* Title */
|
||||||
|
_pdf_add_text_line(stream_buf, sizeof(stream_buf), &slen, &y, 14, "%s", heat_exchanger_name);
|
||||||
|
/* Separator */
|
||||||
|
slen += snprintf(stream_buf + slen, sizeof(stream_buf) - (size_t)slen,
|
||||||
|
"50 %.0f m 550 %.0f l 0.5 0.5 0.5 RG 1 w S\n", y + 8, y + 8);
|
||||||
|
y -= 8;
|
||||||
|
|
||||||
|
_pdf_add_text_line(stream_buf, sizeof(stream_buf), &slen, &y, 11, "P/N: %s", part_number);
|
||||||
|
_pdf_add_text_line(stream_buf, sizeof(stream_buf), &slen, &y, 11, "Side: %s", side_name);
|
||||||
|
_pdf_add_text_line(stream_buf, sizeof(stream_buf), &slen, &y, 11, "Airflow: %0.2f LB/MIN", airflow);
|
||||||
|
_pdf_add_text_line(stream_buf, sizeof(stream_buf), &slen, &y, 11, "Static inlet pressure: %0.2f %s", airflow_pressure, unit);
|
||||||
|
|
||||||
|
if (recalculated_required)
|
||||||
|
{
|
||||||
|
_pdf_add_text_line(stream_buf, sizeof(stream_buf), &slen, &y, 11, "Barometric pressure: %0.2f MBAR", *barometric_pressure);
|
||||||
|
_pdf_add_text_line(stream_buf, sizeof(stream_buf), &slen, &y, 11, "Airflow temperature: %0.2f C", *airflow_temperature);
|
||||||
|
_pdf_add_text_line(stream_buf, sizeof(stream_buf), &slen, &y, 11, "Corrected static pressure: %0.2f %s", *corrected_airflow_pressure, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
_pdf_add_text_line(stream_buf, sizeof(stream_buf), &slen, &y, 11, "Max allowed pressure drop: %0.2f %s", pressure_drop_limit, unit);
|
||||||
|
y -= 6;
|
||||||
|
_pdf_add_text_line(stream_buf, sizeof(stream_buf), &slen, &y, 11, "Test result: %s", test_result);
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
y -= 6;
|
||||||
|
_pdf_add_text_line(stream_buf, sizeof(stream_buf), &slen, &y, 8, "Report date: %s", date_str);
|
||||||
|
|
||||||
|
unsigned int stream_len = (unsigned int)slen;
|
||||||
|
|
||||||
|
/* --- Write PDF file --- */
|
||||||
|
/* Object layout:
|
||||||
|
* 1 0 obj — Catalog
|
||||||
|
* 2 0 obj — Pages
|
||||||
|
* 3 0 obj — Page
|
||||||
|
* 4 0 obj — Content stream
|
||||||
|
* 5 0 obj — Font
|
||||||
|
*/
|
||||||
|
fprintf(f, "%%PDF-1.4\n");
|
||||||
|
|
||||||
|
unsigned int offsets[6] = {0};
|
||||||
|
|
||||||
|
offsets[1] = (unsigned int)ftell(f);
|
||||||
|
fprintf(f, "1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
|
||||||
|
|
||||||
|
offsets[2] = (unsigned int)ftell(f);
|
||||||
|
fprintf(f, "2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n");
|
||||||
|
|
||||||
|
offsets[3] = (unsigned int)ftell(f);
|
||||||
|
fprintf(f, "3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] "
|
||||||
|
"/Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >>\nendobj\n");
|
||||||
|
|
||||||
|
offsets[4] = (unsigned int)ftell(f);
|
||||||
|
fprintf(f, "4 0 obj\n<< /Length %u >>\nstream\n", stream_len);
|
||||||
|
fwrite(stream_buf, 1, stream_len, f);
|
||||||
|
fprintf(f, "\nendstream\nendobj\n");
|
||||||
|
|
||||||
|
offsets[5] = (unsigned int)ftell(f);
|
||||||
|
fprintf(f, "5 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>\nendobj\n");
|
||||||
|
|
||||||
|
/* Cross-reference table */
|
||||||
|
unsigned int xref_offset = (unsigned int)ftell(f);
|
||||||
|
fprintf(f, "xref\n");
|
||||||
|
fprintf(f, "0 6\n");
|
||||||
|
fprintf(f, "0000000000 65535 f \n");
|
||||||
|
for (int i = 1; i <= 5; i++)
|
||||||
|
{
|
||||||
|
fprintf(f, "%010u 00000 n \n", offsets[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(f, "trailer\n<< /Size 6 /Root 1 0 R >>\nstartxref\n");
|
||||||
|
fprintf(f, "%u\n%%%%EOF\n", xref_offset);
|
||||||
|
|
||||||
|
fclose(f);
|
||||||
|
printf("Report saved to: %s\n", filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
void save_report_to_pdf(const char *heat_exchanger_name,
|
||||||
|
const char *part_number,
|
||||||
|
const char *side_name,
|
||||||
|
double airflow,
|
||||||
|
double airflow_pressure,
|
||||||
|
const char *unit,
|
||||||
|
const double *barometric_pressure,
|
||||||
|
const double *airflow_temperature,
|
||||||
|
const double *corrected_airflow_pressure,
|
||||||
|
double pressure_drop_limit,
|
||||||
|
const char *test_result,
|
||||||
|
bool recalculated_required)
|
||||||
|
{
|
||||||
|
_save_report_to_pdf_impl(heat_exchanger_name, part_number, side_name,
|
||||||
|
airflow, airflow_pressure, unit,
|
||||||
|
barometric_pressure, airflow_temperature,
|
||||||
|
corrected_airflow_pressure, pressure_drop_limit,
|
||||||
|
test_result, recalculated_required);
|
||||||
}
|
}
|
||||||
+44
-1
@@ -204,4 +204,47 @@ void get_airflow_temperature(double *airflow_temperature, const air_passage_data
|
|||||||
* @param[in] airflow_temperature Pointer to store the current airflow temperature 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)
|
* @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);
|
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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Save the test report to a PDF file in the project root.
|
||||||
|
*
|
||||||
|
* Generates a minimal PDF file containing the test summary with P/N and date.
|
||||||
|
* The file is saved as "<P/N>_YYYY-MM-DD.pdf" in the project root directory.
|
||||||
|
*
|
||||||
|
* @param heat_exchanger_name Full heat exchanger description string
|
||||||
|
* @param part_number Part number string (e.g. "182820-1")
|
||||||
|
* @param side_name Air passage side description string
|
||||||
|
* @param airflow Airflow value in LB/MIN
|
||||||
|
* @param airflow_pressure Static inlet pressure value
|
||||||
|
* @param unit Pressure unit string ("KPA" or "PSIG")
|
||||||
|
* @param barometric_pressure Barometric pressure value (may be NULL if not applicable)
|
||||||
|
* @param airflow_temperature Airflow temperature value (may be NULL if not applicable)
|
||||||
|
* @param corrected_airflow_pressure Corrected static inlet pressure (may be NULL if not applicable)
|
||||||
|
* @param pressure_drop_limit Max allowed pressure drop limit
|
||||||
|
* @param test_result "PASSED" or "FAILED"
|
||||||
|
* @param recalculated_required Flag indicating if recalculation was performed
|
||||||
|
*/
|
||||||
|
void save_report_to_pdf(const char *heat_exchanger_name,
|
||||||
|
const char *part_number,
|
||||||
|
const char *side_name,
|
||||||
|
double airflow,
|
||||||
|
double airflow_pressure,
|
||||||
|
const char *unit,
|
||||||
|
const double *barometric_pressure,
|
||||||
|
const double *airflow_temperature,
|
||||||
|
const double *corrected_airflow_pressure,
|
||||||
|
double pressure_drop_limit,
|
||||||
|
const char *test_result,
|
||||||
|
bool recalculated_required);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Ask the user a yes/no question.
|
||||||
|
*
|
||||||
|
* Prompts the user with a yes/no question. Returns true for "yes", false for "no".
|
||||||
|
* Handles EXIT command.
|
||||||
|
*
|
||||||
|
* @param[out] answer Pointer to store the answer (true=yes, false=no, EXIT=quit)
|
||||||
|
* @param[in] prompt Question to display
|
||||||
|
*/
|
||||||
|
void get_yes_no(bool *answer, const char *prompt);
|
||||||
Binary file not shown.
@@ -1,16 +1,10 @@
|
|||||||
#include "ats0002/ats0002.h"
|
#include "ats0002/ats0002.h"
|
||||||
#include "ats0002/21_50_14.h"
|
|
||||||
#include "ats0002/21_52_80.h"
|
|
||||||
#include "ats0002/47_32_06.h"
|
|
||||||
#include "ats0002/47_32_14.h"
|
|
||||||
#include "ats0002/21_53_89.h"
|
#include "ats0002/21_53_89.h"
|
||||||
#include "ats0002/21_53_88.h"
|
#include "ats0002/21_53_88.h"
|
||||||
|
#include "ats0002/21_50_14.h"
|
||||||
|
|
||||||
static const heat_exchanger_data_t _heat_exchanger_data[] = {
|
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)},
|
{"Honeywell Heat Exchanger CMM 21-50-14", heat_exchanger_21_50_14_data, ARRAY_SIZE(heat_exchanger_21_50_14_data)},
|
||||||
{"Honeywell Ram Heat Exchanger CMM 21-52-80", heat_exchanger_21_52_80_data, ARRAY_SIZE(heat_exchanger_21_52_80_data)},
|
|
||||||
{"Honeywell NGS Heat Exchanger CMM 47-32-06", heat_exchanger_47_32_06_data, ARRAY_SIZE(heat_exchanger_47_32_06_data)},
|
|
||||||
{"Honeywell NGS Regen Heat Exchanger CMM 47-32-14", heat_exchanger_47_32_14_data, ARRAY_SIZE(heat_exchanger_47_32_14_data)},
|
|
||||||
{"Liebherr Reheater CMM 21-53-89", heat_exchanger_21_53_89_data, ARRAY_SIZE(heat_exchanger_21_53_89_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)}};
|
{"Liebherr Condenser CMM 21-53-88", heat_exchanger_21_53_88_data, ARRAY_SIZE(heat_exchanger_21_53_88_data)}};
|
||||||
|
|
||||||
@@ -34,15 +28,15 @@ int main(void)
|
|||||||
"* *\n"
|
"* *\n"
|
||||||
"************************************************************\n"
|
"************************************************************\n"
|
||||||
"\n");
|
"\n");
|
||||||
for (uint8_t i = 0; i < ARRAY_SIZE(_heat_exchanger_data); ++i)
|
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("%d. %s\n", i + 1, heat_exchanger_data[i].heat_exchanger_description);
|
||||||
}
|
}
|
||||||
printf("\n");
|
printf("\n");
|
||||||
uint16_t heat_exchanger_number = 0;
|
uint16_t heat_exchanger_number = 0;
|
||||||
ASK_CHOICE(heat_exchanger_number, get_heat_exchanger_number, ARRAY_SIZE(_heat_exchanger_data));
|
ASK_CHOICE(heat_exchanger_number, get_heat_exchanger_number, ARRAY_SIZE(heat_exchanger_data));
|
||||||
printf("\n");
|
printf("\n");
|
||||||
const heat_exchanger_data_t *he = &_heat_exchanger_data[heat_exchanger_number];
|
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)
|
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("%d. %s\n", i + 1, he->heat_exchanger_type_data[i].part_number_description);
|
||||||
@@ -101,6 +95,26 @@ int main(void)
|
|||||||
printf("Test result: %s\n", test_pressure <= pressure_drop_limit ? "PASSED" : "FAILED");
|
printf("Test result: %s\n", test_pressure <= pressure_drop_limit ? "PASSED" : "FAILED");
|
||||||
printf("***************************************************\n");
|
printf("***************************************************\n");
|
||||||
printf("\n");
|
printf("\n");
|
||||||
|
/* Offer to save report as PDF */
|
||||||
|
bool save_pdf = false;
|
||||||
|
get_yes_no(&save_pdf, "Save report to PDF?");
|
||||||
|
if (save_pdf)
|
||||||
|
{
|
||||||
|
save_report_to_pdf(
|
||||||
|
he->heat_exchanger_description,
|
||||||
|
he->heat_exchanger_type_data[part_number].part_number_description,
|
||||||
|
hetd->air_passages_data[air_passage_number].air_passage_description,
|
||||||
|
airflow,
|
||||||
|
airflow_pressure,
|
||||||
|
unit,
|
||||||
|
apd->recalculated_required ? &barometric_pressure : NULL,
|
||||||
|
apd->recalculated_required ? &airflow_temperature : NULL,
|
||||||
|
apd->recalculated_required ? &corrected_airflow_pressure : NULL,
|
||||||
|
pressure_drop_limit,
|
||||||
|
test_pressure <= pressure_drop_limit ? "PASSED" : "FAILED",
|
||||||
|
apd->recalculated_required);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
uint16_t command = 0;
|
uint16_t command = 0;
|
||||||
get_user_command(&command);
|
get_user_command(&command);
|
||||||
EXIT_CHECK(command);
|
EXIT_CHECK(command);
|
||||||
|
|||||||
Binary file not shown.
+1
-1
@@ -1 +1 @@
|
|||||||
1.3.0
|
1.0.0
|
||||||
@@ -1,325 +0,0 @@
|
|||||||
# ATS0002 — Расчёт падения давления воздуха в теплообменнике
|
|
||||||
|
|
||||||
**ATS0002** — это программа для расчёта падения давления воздуха на теплообменниках аэрокосмического оборудования.
|
|
||||||
|
|
||||||
Программа поддерживает 6 моделей теплообменников от Honeywell и Liebherr, обеспечивает ввод параметров воздушного потока и статического давления на входе, автоматическую коррекцию показаний с учётом барометрического давления и температуры, а также оценку результата «пройдено/не пройдено» по допустимому пределу падения давления.
|
|
||||||
|
|
||||||
## Основные возможности
|
|
||||||
|
|
||||||
1. **Поддержка 6 моделей теплообменников** — Honeywell CMM 21-50-14, Honeywell Ram CMM 21-52-80, Honeywell NGS CMM 47-32-06, Honeywell NGS Regen CMM 47-32-14, Liebherr Reheater CMM 21-53-89, Liebherr Condenser CMM 21-53-88.
|
|
||||||
2. **Несколько номеров деталей (P/N)** для каждого теплообменника с индивидуальными характеристиками.
|
|
||||||
3. **Конфигурация воздушных каналов** — выбор стороны обдува (охлаждающая, горячая, primary bleed, main bleed и т.д.).
|
|
||||||
4. **Ввод расхода воздуха и статического давления** с валидацией в пределах допустимых диапазонов.
|
|
||||||
5. **Коррекция давления** по барометрическому давлению и температуре воздуха для получения скорректированного значения.
|
|
||||||
6. **Логарифмическая интерполяция** предела падения давления по якорным точкам (anchor points).
|
|
||||||
7. **Автоматическая оценка** «PASSED» или «FAILED» по допустимому пределу.
|
|
||||||
8. **Поддержка единиц KPA и PSIG** — в зависимости от конфигурации теплообменника.
|
|
||||||
9. **Поддержка запятых как десятичного разделителя** — автоматическая нормализация в точку.
|
|
||||||
|
|
||||||
## Внутреннее устройство
|
|
||||||
|
|
||||||
### Иерархия структур данных
|
|
||||||
|
|
||||||
Данные теплообменников организованы в трёхуровневую иерархию:
|
|
||||||
|
|
||||||
| Уровень | Структура | Описание |
|
|
||||||
|---------|-----------|----------|
|
|
||||||
| 1 | `heat_exchanger_data_t` | Модель теплообменника (описание + массив типов) |
|
|
||||||
| 2 | `heat_exchanger_type_data_t` | Номер детали (P/N) — описание + массив воздушных каналов |
|
|
||||||
| 3 | `air_passage_data_t` | Воздушный канал — описание + якорные точки + лимиты |
|
|
||||||
|
|
||||||
**`airflow_to_pressure_anchors_data_t`** — пара «расход-давление», используемая как якорная точка для интерполяции:
|
|
||||||
|
|
||||||
| Поле | Тип | Описание |
|
|
||||||
|------|-----|----------|
|
|
||||||
| `airflow` | `double` | Расход воздуха (LB/MIN) |
|
|
||||||
| `pressure` | `double` | Соответствующее падение давления (KPA или PSIG) |
|
|
||||||
|
|
||||||
**`air_passage_data_t`** — конфигурация одного воздушного канала:
|
|
||||||
|
|
||||||
| Поле | Тип | Описание |
|
|
||||||
|------|-----|----------|
|
|
||||||
| `air_passage_description` | `const char *` | Человеко-читаемое описание канала |
|
|
||||||
| `air_passage_anchors` | `const airflow_to_pressure_anchors_data_t *` | Указатель на массив якорных точек |
|
|
||||||
| `anchors_data_size` | `const uint8_t` | Количество якорных точек |
|
|
||||||
| `recalculated_required` | `const bool` | Флаг необходимости коррекции давления |
|
|
||||||
| `airflow_pressure_unit` | `const airflow_pressure_unit_t` | Единицы давления (KPA или PSIG) |
|
|
||||||
| `min_barometric_pressure` | `const double` | Минимальное барометрическое давление (MBAR) |
|
|
||||||
| `max_barometric_pressure` | `const double` | Максимальное барометрическое давление (MBAR) |
|
|
||||||
| `min_airflow_temperature` | `const double` | Минимальная температура воздуха (°C) |
|
|
||||||
| `max_airflow_temperature` | `const double` | Максимальная температура воздуха (°C) |
|
|
||||||
| `min_airflow_pressure` | `const double` | Минимальное давление воздушного потока |
|
|
||||||
| `max_airflow_pressure` | `const double` | Максимальное давление воздушного потока |
|
|
||||||
|
|
||||||
**`heat_exchanger_type_data_t`** — конфигурация типа теплообменника (номера деталей):
|
|
||||||
|
|
||||||
| Поле | Тип | Описание |
|
|
||||||
|------|-----|----------|
|
|
||||||
| `part_number_description` | `const char *` | Описание номера детали |
|
|
||||||
| `air_passages_data` | `const air_passage_data_t *` | Указатель на массив конфигураций воздушных каналов |
|
|
||||||
| `air_passages_data_size` | `const uint8_t` | Количество воздушных каналов |
|
|
||||||
|
|
||||||
**`heat_exchanger_data_t`** — конфигурация модели теплообменника:
|
|
||||||
|
|
||||||
| Поле | Тип | Описание |
|
|
||||||
|------|-----|----------|
|
|
||||||
| `heat_exchanger_description` | `const char *` | Описание модели теплообменника |
|
|
||||||
| `heat_exchanger_type_data` | `const heat_exchanger_type_data_t *` | Указатель на массив номеров деталей |
|
|
||||||
| `heat_exchanger_type_data_size` | `const uint8_t` | Количество номеров деталей |
|
|
||||||
|
|
||||||
### Управление памятью
|
|
||||||
|
|
||||||
Все данные теплообменников объявлены как `static const` и хранятся в сегменте данных исполняемого файла. Динамическое выделение памяти не используется — компонент не требует `malloc`/`free`.
|
|
||||||
|
|
||||||
### Глобальная таблица теплообменников
|
|
||||||
|
|
||||||
В файле `heat_exchander_airflow_pressure_drop.c` определяется массив `heat_exchanger_data[]` из 6 элементов, связывающий описания моделей с их конфигурационными данными:
|
|
||||||
|
|
||||||
| Индекс | Модель |
|
|
||||||
|--------|--------|
|
|
||||||
| 0 | Honeywell Heat Exchanger CMM 21-50-14 |
|
|
||||||
| 1 | Honeywell Ram Heat Exchanger CMM 21-52-80 |
|
|
||||||
| 2 | Honeywell NGS Heat Exchanger CMM 47-32-06 |
|
|
||||||
| 3 | Honeywell NGS Regen Heat Exchanger CMM 47-32-14 |
|
|
||||||
| 4 | Liebherr Reheater CMM 21-53-89 |
|
|
||||||
| 5 | Liebherr Condenser CMM 21-53-88 |
|
|
||||||
|
|
||||||
## API (интерфейс)
|
|
||||||
|
|
||||||
### Ввод команд
|
|
||||||
|
|
||||||
| Функция | Описание |
|
|
||||||
|---------|----------|
|
|
||||||
| `get_user_command()` | Ввод команды пользователя: числовой выбор, `q`/`Q` для выхода (EXIT), `r`/`R` для повтора (REPEAT) |
|
|
||||||
|
|
||||||
### Выбор параметров
|
|
||||||
|
|
||||||
| Функция | Описание |
|
|
||||||
|---------|----------|
|
|
||||||
| `get_heat_exchanger_number()` | Ввод номера модели теплообменника из доступных вариантов |
|
|
||||||
| `get_heat_exchanger_part_number()` | Ввод номера детали (P/N) для выбранной модели |
|
|
||||||
| `get_heat_exchanger_air_passage_number()` | Ввод номера воздушного канала для выбранной детали |
|
|
||||||
|
|
||||||
### Ввод физических параметров
|
|
||||||
|
|
||||||
| Функция | Описание |
|
|
||||||
|---------|----------|
|
|
||||||
| `get_airflow()` | Ввод расхода воздуха (LB/MIN) с валидацией по диапазону якорных точек |
|
|
||||||
| `get_airflow_pressure()` | Ввод статического давления на входе (KPA или PSIG) с валидацией по лимитам |
|
|
||||||
| `get_barometric_pressure()` | Ввод барометрического давления (MBAR) с валидацией по лимитам |
|
|
||||||
| `get_airflow_temperature()` | Ввод температуры воздуха (°C) с валидацией по лимитам |
|
|
||||||
|
|
||||||
### Расчёт
|
|
||||||
|
|
||||||
| Функция | Описание |
|
|
||||||
|---------|----------|
|
|
||||||
| `get_pressure_drop()` | Расчёт предела падения давления: при необходимости — коррекция давления по барометрии и температуре, затем логарифмическая интерполяция по якорным точкам |
|
|
||||||
|
|
||||||
## Потокобезопасность
|
|
||||||
|
|
||||||
Компонент **не является потокобезопасным**. Программа работает в однопоточном режиме и не использует механизмы синхронизации (мьютексы, критические секции). Все функции работают с глобальным стандартным вводом (`stdin`) и не содержат разделяемого состояния, кроме `static` константных данных.
|
|
||||||
|
|
||||||
## Алгоритмы
|
|
||||||
|
|
||||||
### Логарифмическая интерполяция предела падения давления
|
|
||||||
|
|
||||||
Алгоритм `_get_log_interpolated_airflow_pressure_drop_limit()` вычисляет допустимое падение давления для заданного расхода воздуха методом линейной интерполяции в логарифмическом пространстве между двумя ближайшими якорными точками:
|
|
||||||
|
|
||||||
- Если расход воздуха **меньше или равен** первой якорной точке — возвращается давление первой точки.
|
|
||||||
- Если расход воздуха **больше или равен** последней якорной точке — возвращается давление последней точки.
|
|
||||||
- Для расхода внутри диапазона:
|
|
||||||
- Вычисляются натуральные логарифмы: `log_x = log(airflow)`, `log_x1 = log(airflow[i])`, `log_x2 = log(airflow[i+1])`, `log_y1 = log(pressure[i])`, `log_y2 = log(pressure[i+1])`.
|
|
||||||
- Выполняется линейная интерполяция: `log_y = log_y1 + (log_x - log_x1) * (log_y2 - log_y1) / (log_x2 - log_x1)`.
|
|
||||||
- Результат: `limit = exp(log_y)`.
|
|
||||||
|
|
||||||
### Коррекция давления по плотности
|
|
||||||
|
|
||||||
Функция `_calculate_density_corrected_pressure()` корректирует показание давления с учётом барометрического давления и температуры по формуле:
|
|
||||||
|
|
||||||
1. `value_4 = airflow_pressure * 0.5`
|
|
||||||
2. `value_5 = barometric_pressure + value_4`
|
|
||||||
3. `value_6 = airflow_temperature + 273.2` (перевод в Кельвины)
|
|
||||||
4. `value_7 = (0.28443 * value_5) / value_6`
|
|
||||||
5. `corrected_airflow_pressure = airflow_pressure * value_7`
|
|
||||||
|
|
||||||
Результат вычисляется в **гектопаскалях (HPA)**. При входе в `get_pressure_drop()` результат конвертируется обратно в исходные единицы (KPA или PSIG):
|
|
||||||
|
|
||||||
- **KPA**: деление на 10.0
|
|
||||||
- **PSIG**: умножение на 0.0145038
|
|
||||||
|
|
||||||
## Примеры использования
|
|
||||||
|
|
||||||
### Базовый сценарий
|
|
||||||
|
|
||||||
```c
|
|
||||||
#include "ats0002/ats0002.h"
|
|
||||||
#include "ats0002/21_50_14.h"
|
|
||||||
|
|
||||||
// Глобальная таблица теплообменников определяется в main-файле.
|
|
||||||
// Для демонстрации — прямой вызов функций ввода и расчёта:
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
setlocale(LC_NUMERIC, "C");
|
|
||||||
|
|
||||||
// 1. Выбор теплообменника (индекс 0 — Honeywell CMM 21-50-14)
|
|
||||||
const heat_exchanger_data_t *he = &heat_exchanger_data[0];
|
|
||||||
|
|
||||||
// 2. Выбор номера детали (индекс 0 — 182820-1)
|
|
||||||
const heat_exchanger_type_data_t *hetd = &he->heat_exchanger_type_data[0];
|
|
||||||
|
|
||||||
// 3. Выбор воздушного канала (индекс 0 — Cooling Air Side)
|
|
||||||
const air_passage_data_t *apd = &hetd->air_passages_data[0];
|
|
||||||
|
|
||||||
// 4. Ввод параметров (в реальном коде — через get_* функции)
|
|
||||||
double airflow = 50.0; // LB/MIN
|
|
||||||
double airflow_pressure = 0.33; // KPA
|
|
||||||
|
|
||||||
double barometric_pressure = 1013.25; // MBAR
|
|
||||||
double airflow_temperature = 20.0; // °C
|
|
||||||
|
|
||||||
double pressure_drop_limit = 0.0;
|
|
||||||
double corrected_airflow_pressure = 0.0;
|
|
||||||
|
|
||||||
// 5. Расчёт предела падения давления
|
|
||||||
get_pressure_drop(&pressure_drop_limit, &airflow, &airflow_pressure,
|
|
||||||
&corrected_airflow_pressure, &barometric_pressure,
|
|
||||||
&airflow_temperature, apd);
|
|
||||||
|
|
||||||
// 6. Оценка результата
|
|
||||||
bool passed = corrected_airflow_pressure <= pressure_drop_limit;
|
|
||||||
printf("Test result: %s\n", passed ? "PASSED" : "FAILED");
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Работа с разными единицами (PSIG)
|
|
||||||
|
|
||||||
```c
|
|
||||||
#include "ats0002/ats0002.h"
|
|
||||||
#include "ats0002/21_50_14.h"
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
setlocale(LC_NUMERIC, "C");
|
|
||||||
|
|
||||||
const heat_exchanger_data_t *he = &heat_exchanger_data[0];
|
|
||||||
const heat_exchanger_type_data_t *hetd = &he->heat_exchanger_type_data[0];
|
|
||||||
|
|
||||||
// Индекс 1 — Bleed Air Side (использует PSIG)
|
|
||||||
const air_passage_data_t *apd = &hetd->air_passages_data[1];
|
|
||||||
|
|
||||||
double airflow = 50.0; // LB/MIN
|
|
||||||
double airflow_pressure = 3.05; // PSIG
|
|
||||||
|
|
||||||
double barometric_pressure = 1013.25; // MBAR
|
|
||||||
double airflow_temperature = 20.0; // °C
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
printf("Max allowed: %.2f PSIG\n", pressure_drop_limit);
|
|
||||||
printf("Corrected: %.2f PSIG\n", corrected_airflow_pressure);
|
|
||||||
printf("Result: %s\n",
|
|
||||||
corrected_airflow_pressure <= pressure_drop_limit ? "PASSED" : "FAILED");
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Использование без коррекции (Liebherr Reheater)
|
|
||||||
|
|
||||||
```c
|
|
||||||
#include "ats0002/ats0002.h"
|
|
||||||
#include "ats0002/21_53_89.h"
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
setlocale(LC_NUMERIC, "C");
|
|
||||||
|
|
||||||
const heat_exchanger_data_t *he = &heat_exchanger_data[4];
|
|
||||||
const heat_exchanger_type_data_t *hetd = &he->heat_exchanger_type_data[0];
|
|
||||||
const air_passage_data_t *apd = &hetd->air_passages_data[0];
|
|
||||||
|
|
||||||
// У Liebherr Reheater recalculated_required = false,
|
|
||||||
// поэтому коррекция не требуется.
|
|
||||||
double airflow = 50.0; // LB/MIN
|
|
||||||
double airflow_pressure = 0.37; // PSIG (без коррекции)
|
|
||||||
|
|
||||||
double pressure_drop_limit = 0.0;
|
|
||||||
double corrected_airflow_pressure = 0.0; // Не используется
|
|
||||||
|
|
||||||
get_pressure_drop(&pressure_drop_limit, &airflow, &airflow_pressure,
|
|
||||||
&corrected_airflow_pressure, NULL, NULL, apd);
|
|
||||||
|
|
||||||
printf("Max allowed: %.2f PSIG\n", pressure_drop_limit);
|
|
||||||
printf("Result: %s\n",
|
|
||||||
airflow_pressure <= pressure_drop_limit ? "PASSED" : "FAILED");
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Логирование
|
|
||||||
|
|
||||||
Компонент не использует ESP-IDF логирование. Все сообщения выводятся через стандартную функцию `printf` в консоль.
|
|
||||||
|
|
||||||
## Ошибки
|
|
||||||
|
|
||||||
| Код | Описание |
|
|
||||||
|-----|----------|
|
|
||||||
| `EXIT` (0xFFFF) | Команда выхода из программы — выводит «Goodbye.» и завершает работу |
|
|
||||||
| `REPEAT` (0xFFEE) | Команда повтора — возвращается к началу цикла ввода без завершения |
|
|
||||||
| `-1.0` | Внутренний код ошибки интерполяции — возвращается если якорные точки не найдены (не должно происходить при валидных данных) |
|
|
||||||
|
|
||||||
## Ограничения
|
|
||||||
|
|
||||||
1. **Однопоточная работа** — программа не поддерживает параллельное выполнение и не использует механизмы синхронизации.
|
|
||||||
2. **Статические данные** — все конфигурационные данные теплообменников зашиты в код. Добавление новых моделей требует изменения исходного кода и перекомпиляции.
|
|
||||||
3. **Текстовый интерфейс** — программа работает исключительно через стандартный ввод/вывод (`stdin`/`stdout`), графический интерфейс отсутствует.
|
|
||||||
4. **Жёстко заданные диапазоны** — лимиты для барометрического давления (840–1070 MBAR) и температуры (15–35 °C или 15–33 °C) фиксированы в конфигурации.
|
|
||||||
5. **Экстраполяция не поддерживается** — если расход воздуха выходит за пределы якорных точек, возвращается значение первой или последней точки без экстраполяции.
|
|
||||||
6. **Зависимость от `setlocale`** — для корректной работы с десятичными разделителями необходимо вызов `setlocale(LC_NUMERIC, "C")`.
|
|
||||||
7. **Максимум 65 535 элементов меню** — выбор элементов используется тип `uint16_t`, что ограничивает количество моделей, деталей и каналов.
|
|
||||||
|
|
||||||
## Требования для сборки
|
|
||||||
|
|
||||||
- C99 или новее (стандарт C11 рекомендуется)
|
|
||||||
- Стандартная библиотека C (`stdlib.h`, `stdio.h`, `string.h`, `math.h`, `locale.h`)
|
|
||||||
- Компилятор GCC (cross-compile для ESP-IDF) или MSVC (для Windows)
|
|
||||||
- Библиотека `libm` (математическая библиотека, подключается флагом `-lm`)
|
|
||||||
|
|
||||||
## Установка
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Клонирование репозитория
|
|
||||||
git clone https://github.com/aZholtikov/ats0002
|
|
||||||
cd ats0002
|
|
||||||
|
|
||||||
# Сборка
|
|
||||||
# Для Linux/macOS:
|
|
||||||
gcc -o ats0002 heat_exchander_airflow_pressure_drop.c \
|
|
||||||
ats0002/ats0002.c -lm
|
|
||||||
|
|
||||||
# Для Windows (MSVC):
|
|
||||||
cl heat_exchander_airflow_pressure_drop.c \
|
|
||||||
ats0002/ats0002.c
|
|
||||||
|
|
||||||
# Запуск
|
|
||||||
./ats0002
|
|
||||||
```
|
|
||||||
|
|
||||||
```c
|
|
||||||
#include "ats0002/ats0002.h"
|
|
||||||
#include "ats0002/21_50_14.h"
|
|
||||||
#include "ats0002/21_52_80.h"
|
|
||||||
#include "ats0002/47_32_06.h"
|
|
||||||
#include "ats0002/47_32_14.h"
|
|
||||||
#include "ats0002/21_53_89.h"
|
|
||||||
#include "ats0002/21_53_88.h"
|
|
||||||
```
|
|
||||||
Reference in new issue
Block a user