Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c79271056 |
No files matched your search
@@ -1,4 +1,6 @@
|
||||
#include "ats0002.h"
|
||||
#include <stdarg.h>
|
||||
#include <time.h>
|
||||
|
||||
/**
|
||||
* @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 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);
|
||||
|
||||
/**
|
||||
@@ -330,4 +357,239 @@ static void _calculate_density_corrected_pressure(double *corrected_airflow_pres
|
||||
double value_6 = *airflow_temperature + 273.2;
|
||||
double value_7 = (0.28443 * value_5) / value_6;
|
||||
*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] 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.
@@ -95,6 +95,26 @@ int main(void)
|
||||
printf("Test result: %s\n", test_pressure <= pressure_drop_limit ? "PASSED" : "FAILED");
|
||||
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;
|
||||
get_user_command(&command);
|
||||
EXIT_CHECK(command);
|
||||
|
||||
Reference in new issue
Block a user