🚀 C# ile Programlamaya Giriş: Kodlamaya İlk Adımını At, Kendi Projelerini Geliştirmeye Başla!
00HOME 01POSTS 02COURSES 03ELECTRİCAL_ENERGY 04AUTOMOTİVE_ENGİNEERİNG 05TOOLS 06LAB 07GLOSSARY 08TAGS ♡MY LIST ⌕SEARCH
TRENG

Mini project: sensor data logger and statistics

Put what you have learned together: a temperature logging program using struct, a class, vector, STL algorithms and lambdas.

You have reached the end of the course. Now let's combine the pieces you have learned into a single program: a small data logger that stores the readings coming from a temperature sensor, computes their statistics and warns when a threshold is exceeded. This is the skeleton of a real engineering tool.

Design

  • The Reading struct: the minute and temperature of a reading.
  • The Logger class: keeps the readings in a vector; provides the average, minimum, maximum, standard deviation and the number of readings above a threshold.
  • main: adds sample data and prints the table and the statistics.

Let's briefly recall the calculation methods. The average is the sum divided by the number of elements. The standard deviation tells how widely the data is spread around the average: take the square of each reading's difference from the average, find the average of those squares, then take the square root. In this program we use the "population" standard deviation, which covers all of the data.

Code

#include <algorithm>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <numeric>
#include <vector>

struct Reading {
    int minute;
    double temperature;
};

class Logger {
public:
    void add(int minute, double temperature) {
        data_.push_back(Reading{minute, temperature});
    }

    bool isEmpty() const { return data_.empty(); }

    double average() const {
        double sum = std::accumulate(
            data_.begin(), data_.end(), 0.0,
            [](double t, const Reading& r) { return t + r.temperature; });
        return sum / data_.size();
    }

    double standardDeviation() const {
        double m = average();
        double squares = 0.0;
        for (const auto& r : data_) {
            squares += (r.temperature - m) * (r.temperature - m);
        }
        return std::sqrt(squares / data_.size());
    }

    double minimum() const {
        auto it = std::min_element(
            data_.begin(), data_.end(),
            [](const Reading& a, const Reading& b) { return a.temperature < b.temperature; });
        return it->temperature;
    }

    double maximum() const {
        auto it = std::max_element(
            data_.begin(), data_.end(),
            [](const Reading& a, const Reading& b) { return a.temperature < b.temperature; });
        return it->temperature;
    }

    int countAbove(double threshold) const {
        return static_cast<int>(std::count_if(
            data_.begin(), data_.end(),
            [threshold](const Reading& r) { return r.temperature > threshold; }));
    }

    void printTable() const {
        std::cout << std::setw(6) << "min" << std::setw(10) << "temp" << '\n';
        for (const auto& r : data_) {
            std::cout << std::setw(6) << r.minute
                      << std::setw(10) << r.temperature << '\n';
        }
    }

private:
    std::vector<Reading> data_;
};

int main() {
    Logger logger;
    logger.add(0, 22.5);
    logger.add(5, 23.0);
    logger.add(10, 21.5);
    logger.add(15, 24.0);
    logger.add(20, 22.0);

    if (logger.isEmpty()) {
        std::cout << "No data.\n";
        return 1;
    }

    const double threshold = 23.5;
    std::cout << std::fixed << std::setprecision(2);
    logger.printTable();
    std::cout << '\n';
    std::cout << "Average  : " << logger.average() << '\n';
    std::cout << "Minimum  : " << logger.minimum() << '\n';
    std::cout << "Maximum  : " << logger.maximum() << '\n';
    std::cout << "Std dev  : " << logger.standardDeviation() << '\n';

    int exceeded = logger.countAbove(threshold);
    std::cout << "Above threshold (" << threshold << "): " << exceeded << " reading(s)\n";
    if (exceeded > 0) {
        std::cout << "WARNING: temperature threshold exceeded!\n";
    }
    return 0;
}
   min      temp
     0     22.50
     5     23.00
    10     21.50
    15     24.00
    20     22.00

Average  : 22.60
Minimum  : 21.50
Maximum  : 24.00
Std dev  : 0.86
Above threshold (23.50): 1 reading(s)
WARNING: temperature threshold exceeded!

Let's verify the calculation by hand

The sum is 113.0, there are 5 elements, and the average is 22.6. The differences from the average: -0.1, 0.4, -1.1, 1.4, -0.6. Their squares: 0.01, 0.16, 1.21, 1.96, 0.36; their sum is 3.70. Dividing by 5 gives 0.74, and its square root is about 0.86. The program agrees with this result. Checking your own calculation code against a small example worked out by hand is a golden rule in engineering.

What did we use where?

  • Class and encapsulation: data_ is private; from outside there are only the interface methods.
  • const methods: The statistics methods don't change the log.
  • STL: accumulate, min_element, max_element, count_if.
  • Lambdas: The comparison and threshold rules were written in place; an outside variable was captured with [threshold].
  • const references: With const auto& in the loop, nothing was copied.
  • Validation: Exiting on an empty log without dividing.

On a real device the add method would take the value you read from the ADC, and the data would be kept in a ring buffer (circular buffer). That way memory never grows; the oldest reading is overwritten by the newest one. Building this design with a fixed-size std::array is your first step toward the embedded world.

In this program average() divides by zero on an empty log. main checks for this beforehand, but someone else using the class might forget. In robust code the methods themselves should handle the empty case (e.g. by throwing an exception or returning a std::optional).

Try it yourself

  1. Extend the program to read input with std::cin: the user enters a minute and a temperature, and it stops when the minute is -1.
  2. Add a method that sorts the readings by temperature from largest to smallest and prints the top three.
  3. Add a method that writes the readings to a file (std::ofstream, header <fstream>).
  4. Write a function that converts a sensor's ADC value into a voltage and then a temperature (e.g. for the LM35 sensor, which gives 10 mV/°C, temperature = voltage * 100).
  5. Challenge: Write a method that calculates the moving average of the last 3 readings.

What's next?

With this course you have completed the fundamentals of C++. Next you can look into: inheritance and virtual functions (polymorphism), templates, exceptions (try/catch), file handling, multithreading and project management with CMake. If you want to head toward embedded systems, getting an STM32 or ESP32 development board and writing your first LED blink program in C++ is a great next step.

// Mini quiz

Click an option; you will see right away, with the reason, whether it is correct or not.

1. In this program, what does the standard deviation measure?
2. What is the benefit of making the data_ member private in the Logger class?
3. What happens if average() is called on an empty log?

Mark it when you finish: your progress is saved and you earn +20 XP.

// Final exam and certificate

20 random questions from the lessons, 30 minutes, pass mark 70%. If you pass, you get a verifiable certificate of achievement (with a QR code).

Take the final exam

Tip: ← → keys let you move between lessons.