Implementation & Test Code

Need: (CODE) Implementation is traced into source files. CODE_IMPL_001 _images/arrow-right-circle.svg
status: active
tags: implementation, code, traceability

The implementation is part of the traceability chain by:

  • linking code-level needs (CODE_*) to REQ/ARCH/TEST needs

  • embedding source excerpts (so the shipment HTML is self-contained)

  • attaching downloadable source files (so auditors can inspect full context)

Source files (download from the built docs):

Included sources:

#pragma once

#include <stdbool.h>
#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

// Temperature in 0.1°C units (e.g., 100.0°C => 1000)
typedef int16_t tsim_temp_x10_t;

typedef enum {
    TSIM_STATE_SAFE = 0,
    TSIM_STATE_UNSAFE = 1,
} tsim_state_t;

// REQ_FUNC_001: ADC (12-bit) to temperature conversion
// Output range: -40.0°C..+125.0°C => -400..1250 (0.1°C)
tsim_temp_x10_t tsim_adc_to_temp_x10(uint16_t adc_counts);

// REQ_FUNC_002: 5-sample moving average filter
typedef struct {
    tsim_temp_x10_t window[5];
    uint8_t count;
    uint8_t index;
    int32_t sum;
} tsim_filter_t;

void tsim_filter_init(tsim_filter_t* filter);
// Returns true when output is valid (after 5 samples)
bool tsim_filter_update(tsim_filter_t* filter, tsim_temp_x10_t sample, tsim_temp_x10_t* out_filtered);

// REQ_FUNC_003/004: threshold + hysteresis state machine
typedef struct {
    tsim_temp_x10_t threshold_high_x10;
    tsim_temp_x10_t threshold_low_x10;
    tsim_state_t state;
} tsim_sm_t;

void tsim_sm_init(tsim_sm_t* sm, tsim_temp_x10_t high_x10, tsim_temp_x10_t low_x10);
tsim_state_t tsim_sm_evaluate(tsim_sm_t* sm, tsim_temp_x10_t filtered_temp_x10);

#ifdef __cplusplus
}
#endif
#include "tsim.h"

#include <stddef.h>

/*
OSQAR-CODE-TRACE (implementation tags)

REQ: REQ_SAFETY_001 REQ_SAFETY_002 REQ_SAFETY_003 REQ_FUNC_001 REQ_FUNC_002 REQ_FUNC_003 REQ_FUNC_004
ARCH: ARCH_001 ARCH_DESIGN_001 ARCH_DESIGN_002 ARCH_DESIGN_003 ARCH_ERROR_001 ARCH_ERROR_002 ARCH_FUNC_001 ARCH_FUNC_002 ARCH_FUNC_003 ARCH_SEOOC_001 ARCH_SEOOC_002 ARCH_SIGNAL_001 ARCH_SIGNAL_002 ARCH_SIGNAL_003
*/

// Linear mapping of ADC counts (0..4095) to Celsius (-40..125)
// Returned as 0.1°C integer.

tsim_temp_x10_t tsim_adc_to_temp_x10(uint16_t adc_counts) {
    if (adc_counts > 4095u) {
        // Hold last safe state behavior is integration-specific; for this example clamp.
        adc_counts = 4095u;
    }

    // Use integer math with rounding to nearest.
    // celsius = -40 + adc * (165 / 4095)
    // x10: temp_x10 = -400 + adc * (1650 / 4095)
    const int32_t numerator = (int32_t)adc_counts * 1650;
    const int32_t scaled = (numerator + 2047) / 4095; // round
    int32_t temp_x10 = -400 + scaled;

    if (temp_x10 < -400) temp_x10 = -400;
    if (temp_x10 > 1250) temp_x10 = 1250;

    return (tsim_temp_x10_t)temp_x10;
}

void tsim_filter_init(tsim_filter_t* filter) {
    if (!filter) return;
    for (size_t i = 0; i < 5; i++) {
        filter->window[i] = 0;
    }
    filter->count = 0;
    filter->index = 0;
    filter->sum = 0;
}

bool tsim_filter_update(tsim_filter_t* filter, tsim_temp_x10_t sample, tsim_temp_x10_t* out_filtered) {
    if (!filter) return false;

    if (filter->count < 5) {
        filter->window[filter->index] = sample;
        filter->sum += sample;
        filter->index = (uint8_t)((filter->index + 1u) % 5u);
        filter->count++;
        return false;
    }

    // Replace oldest sample
    const tsim_temp_x10_t old = filter->window[filter->index];
    filter->sum -= old;
    filter->window[filter->index] = sample;
    filter->sum += sample;
    filter->index = (uint8_t)((filter->index + 1u) % 5u);

    if (out_filtered) {
        *out_filtered = (tsim_temp_x10_t)(filter->sum / 5);
    }

    return true;
}

void tsim_sm_init(tsim_sm_t* sm, tsim_temp_x10_t high_x10, tsim_temp_x10_t low_x10) {
    if (!sm) return;
    sm->threshold_high_x10 = high_x10;
    sm->threshold_low_x10 = low_x10;
    sm->state = TSIM_STATE_SAFE;
}

tsim_state_t tsim_sm_evaluate(tsim_sm_t* sm, tsim_temp_x10_t filtered_temp_x10) {
    if (!sm) return TSIM_STATE_SAFE;

    if (sm->state == TSIM_STATE_SAFE) {
        if (filtered_temp_x10 >= sm->threshold_high_x10) {
            sm->state = TSIM_STATE_UNSAFE;
        }
    } else {
        if (filtered_temp_x10 <= sm->threshold_low_x10) {
            sm->state = TSIM_STATE_SAFE;
        }
    }

    return sm->state;
}
Need: (TEST) Test suite provides coverage of requirements. TEST_CODE_001 _images/arrow-right-circle.svg
status: active
tags: verification, testing, code

The native test runner writes test_results.xml in JUnit format and maps to the TEST_* needs.

#include "tsim.h"
#include "osqar_shared.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*
OSQAR-CODE-TRACE (test tags)

TEST: TEST_CODE_001 TEST_VERIFY_001 TEST_METHOD_001 TEST_METHOD_002 TEST_METHOD_003 TEST_CONVERSION_001 TEST_FILTER_001 TEST_THRESHOLD_001 TEST_HYSTERESIS_001 TEST_END_TO_END_001 TEST_ERROR_RECOVERY_001 TEST_FAIL_SAFE_001 TEST_EXEC_001 TEST_REPORT_001
*/

typedef struct {
    const char* name;
    bool passed;
    char message[256];
} test_result_t;

static void set_fail(test_result_t* r, const char* msg) {
    r->passed = false;
    strncpy(r->message, msg, sizeof(r->message) - 1);
    r->message[sizeof(r->message) - 1] = '\0';
}

static test_result_t test_conversion_full_range(void) {
    // TEST_CONVERSION_001
    test_result_t r = {"test_conversion_full_range", true, {0}};

    struct { uint16_t adc; tsim_temp_x10_t expected; int16_t tol; } cases[] = {
        {0u, -400, 10},
        {2048u, 425, 10},
        {4095u, 1250, 10},
    };

    for (size_t i = 0; i < sizeof(cases)/sizeof(cases[0]); i++) {
        const tsim_temp_x10_t got = tsim_adc_to_temp_x10(cases[i].adc);
        const int32_t diff = (int32_t)got - (int32_t)cases[i].expected;
        if (diff > cases[i].tol || diff < -cases[i].tol) {
            char buf[256];
            snprintf(buf, sizeof(buf), "ADC %u => %d, expected %d±%d", cases[i].adc, got, cases[i].expected, cases[i].tol);
            set_fail(&r, buf);
            return r;
        }
    }

    return r;
}

static test_result_t test_filter_noise_rejection(void) {
    // TEST_FILTER_001
    test_result_t r = {"test_filter_noise_rejection", true, {0}};

    const tsim_temp_x10_t noisy[] = {500, 600, 450, 550, 500, 480, 520, 490};
    tsim_filter_t f;
    tsim_filter_init(&f);

    tsim_temp_x10_t outputs[16];
    size_t out_count = 0;

    for (size_t i = 0; i < sizeof(noisy)/sizeof(noisy[0]); i++) {
        tsim_temp_x10_t out;
        if (tsim_filter_update(&f, noisy[i], &out)) {
            outputs[out_count++] = out;
        }
    }

    if (out_count < 1) {
        set_fail(&r, "Filter produced no outputs");
        return r;
    }

    // outputs should be near nominal 500
    for (size_t i = 0; i < out_count; i++) {
        if (outputs[i] < 480 || outputs[i] > 520) {
            set_fail(&r, "Filtered output out of expected band (480..520)");
            return r;
        }
    }

    return r;
}

static test_result_t test_threshold_and_hysteresis(void) {
    // TEST_THRESHOLD_001 + TEST_HYSTERESIS_001
    test_result_t r = {"test_threshold_and_hysteresis", true, {0}};

    tsim_sm_t sm;
    tsim_sm_init(&sm, 1000, 950);

    if (sm.state != TSIM_STATE_SAFE) {
        set_fail(&r, "Initial state must be SAFE");
        return r;
    }

    if (tsim_sm_evaluate(&sm, 999) != TSIM_STATE_SAFE) {
        set_fail(&r, "Must remain SAFE at 99.9C");
        return r;
    }

    if (tsim_sm_evaluate(&sm, 1000) != TSIM_STATE_UNSAFE) {
        set_fail(&r, "Must transition to UNSAFE at 100.0C");
        return r;
    }

    if (tsim_sm_evaluate(&sm, 990) != TSIM_STATE_UNSAFE) {
        set_fail(&r, "Must remain UNSAFE at 99.0C due to hysteresis");
        return r;
    }

    if (tsim_sm_evaluate(&sm, 950) != TSIM_STATE_SAFE) {
        set_fail(&r, "Must recover to SAFE at 95.0C");
        return r;
    }

    return r;
}

static test_result_t test_shared_magic_constant(void) {
    // TEST_METHOD_001
    test_result_t r = {"test_shared_magic_constant", true, {0}};

    const int got = osqar_shared_magic();
    if (got != 42) {
        char buf[256];
        snprintf(buf, sizeof(buf), "osqar_shared_magic() => %d, expected 42", got);
        set_fail(&r, buf);
        return r;
    }

    return r;
}

static void write_junit(const char* path, const test_result_t* results, size_t count) {
    FILE* f = fopen(path, "w");
    if (!f) {
        fprintf(stderr, "Failed to open %s for writing\n", path);
        exit(2);
    }

    size_t failures = 0;
    for (size_t i = 0; i < count; i++) {
        if (!results[i].passed) failures++;
    }

    fprintf(f, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
    fprintf(
        f,
        "<testsuite name=\"tsim_c\" tests=\"%zu\" failures=\"%zu\" errors=\"0\" skipped=\"0\" time=\"0\">\n",
        count,
        failures
    );

    for (size_t i = 0; i < count; i++) {
        fprintf(f, "  <testcase classname=\"tsim_c\" name=\"%s\" time=\"0\">\n", results[i].name);
        if (!results[i].passed) {
            fprintf(f, "    <failure message=\"%s\"/>\n", results[i].message[0] ? results[i].message : "failed");
        }
        fprintf(f, "  </testcase>\n");
    }

    fprintf(f, "</testsuite>\n");
    fclose(f);
}

int main(int argc, char** argv) {
    const char* out = (argc >= 2) ? argv[1] : "test_results.xml";

    test_result_t results[] = {
        test_conversion_full_range(),
        test_filter_noise_rejection(),
        test_threshold_and_hysteresis(),
        test_shared_magic_constant(),
    };

    const size_t count = sizeof(results)/sizeof(results[0]);
    write_junit(out, results, count);

    for (size_t i = 0; i < count; i++) {
        if (!results[i].passed) {
            fprintf(stderr, "FAIL: %s: %s\n", results[i].name, results[i].message);
            return 1;
        }
    }

    printf("PASS: %zu tests\n", count);
    return 0;
}
Need: (CODE) Complete example includes both documentation and implementation. CODE_REPO_001 _images/arrow-right-circle.svg
status: active
tags: repository, structure

This example keeps documentation (*.rst) next to the C source and test artifacts for end-to-end traceability.

Code Organization

This TSIM implementation is written in C and keeps the same requirement IDs as the reference example.

examples/c_hello_world/
├── include/
│   └── tsim.h
├── src/
│   └── tsim.c
├── tests/
│   └── test_tsim.c          # Native test runner writing JUnit XML
├── CMakeLists.txt
├── test_results.xml         # Generated by ./build-and-test.sh
└── build-and-test.sh

Traceability in C Code

In C/C++ projects, requirement traceability is typically captured via:

  • structured comments (e.g., REQ_FUNC_001, TEST_CONVERSION_001)

  • module-level headers (interfaces as architecture artifacts)

In OSQAr, you can additionally trace into the actual source by embedding excerpts via literalinclude and attaching full files via :download: (see above).

The functions in include/tsim.h map directly to:

Building & Running Tests

cd examples/c_hello_world

# Build
cmake -S . -B build
cmake --build build

# Run tests (writes JUnit XML)
./build/junit_tests test_results.xml

Then build the documentation (imports test_results.xml):

osqar build-docs --project examples/c_hello_world