Skip to content

Commit 4582520

Browse files
committed
Add runtime monitoring, tests, and docs
0 parents  commit 4582520

16 files changed

Lines changed: 1435 additions & 0 deletions

.gitignore

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
build/
2+
*.obj
3+
*.o
4+
*.pdb
5+
*.ilk
6+
*.exe
7+
*.db
8+
*.csv
9+
*.log
10+
*.user
11+
*.vcxproj*
12+
*.sln
13+
out/

CMakeLists.txt

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
cmake_minimum_required(VERSION 3.16)
2+
project(DeviceSensorDataLogger VERSION 1.0 LANGUAGES CXX)
3+
4+
set(CMAKE_CXX_STANDARD 17)
5+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
6+
set(CMAKE_CXX_EXTENSIONS OFF)
7+
8+
option(ENABLE_SQLITE "Enable SQLite logging support" ON)
9+
include(CTest)
10+
11+
find_package(Threads REQUIRED)
12+
if(ENABLE_SQLITE)
13+
find_package(SQLite3)
14+
endif()
15+
16+
add_library(device_sensor_core
17+
src/TimeUtils.cpp
18+
src/Sensor.cpp
19+
src/LogSink.cpp
20+
src/Stats.cpp
21+
src/DataLogger.cpp
22+
)
23+
24+
target_include_directories(device_sensor_core PUBLIC include)
25+
target_link_libraries(device_sensor_core PUBLIC Threads::Threads)
26+
27+
if(SQLite3_FOUND)
28+
target_compile_definitions(device_sensor_core PRIVATE HAS_SQLITE=1)
29+
target_link_libraries(device_sensor_core PUBLIC SQLite::SQLite3)
30+
else()
31+
message(STATUS "SQLite3 not found. SQLite output will be unavailable.")
32+
endif()
33+
34+
add_executable(device_sensor_logger
35+
src/main.cpp
36+
)
37+
38+
target_link_libraries(device_sensor_logger PRIVATE device_sensor_core)
39+
40+
if(BUILD_TESTING)
41+
add_executable(device_sensor_logger_tests
42+
tests/device_sensor_logger_tests.cpp
43+
)
44+
45+
target_link_libraries(device_sensor_logger_tests PRIVATE device_sensor_core)
46+
add_test(NAME device_sensor_logger_tests COMMAND device_sensor_logger_tests)
47+
endif()

README.md

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# Device Sensor Data Logger (C++)
2+
3+
A C++17 sensor logging application for simulated or serial temperature/light sources. It supports CSV or SQLite persistence, threaded sampling, runtime summaries, threshold alerts, and CSV export.
4+
5+
## Features
6+
7+
- Sensor input modes:
8+
- Simulated sensor mode (Linux/Windows, no hardware needed)
9+
- Serial sensor mode (Linux UART/USB serial device)
10+
- Logging outputs:
11+
- CSV file
12+
- SQLite database (when SQLite3 is available)
13+
- Runtime controls:
14+
- Start logging
15+
- Stop logging
16+
- Show live stats (min/max/avg)
17+
- Show runtime status (accepted/rejected samples, failures, last reading)
18+
- Reset session statistics without restarting the app
19+
- Export logs to CSV
20+
- Safety and observability:
21+
- Input validation rejects invalid readings such as `NaN` and negative lux values
22+
- Configurable alert thresholds for temperature and light
23+
- Optional `--max-samples` auto-stop for bounded runs and tests
24+
- Demonstrates:
25+
- OOP interfaces (`ISensor`, `ILogSink`)
26+
- File I/O and optional SQLite storage
27+
- Threaded periodic logging loop
28+
- Basic serial communication parsing
29+
30+
## Architecture
31+
32+
- `ISensor` abstracts data sources. `SimulatedSensor` generates drifting readings. `SerialSensor` reads `temp,light` lines from a serial device on non-Windows platforms.
33+
- `ILogSink` abstracts persistence. `CsvLogSink` appends CSV rows. `SqliteLogSink` stores rows in a `sensor_log` table and can export them back to CSV.
34+
- `DataLogger` owns one sensor and one sink, runs the sampling loop on a worker thread, tracks aggregated stats, validates samples, counts failures, and raises threshold alerts.
35+
- `StatsAggregator` maintains min/max/average for temperature and light.
36+
37+
## Build
38+
39+
```bash
40+
cmake -S . -B build
41+
cmake --build build
42+
```
43+
44+
## Test
45+
46+
```bash
47+
ctest --test-dir build --output-on-failure
48+
```
49+
50+
The test target covers stats reset logic plus the logger lifecycle, invalid sample rejection, alert tracking, and bounded auto-stop behavior.
51+
52+
## Run
53+
54+
### Interactive mode (simulated sensor + CSV)
55+
56+
```bash
57+
./build/device_sensor_logger --mode sim --output csv --file sensor_log.csv --interval-ms 1000 --temp-alert-max 28 --light-alert-min 100
58+
```
59+
60+
Then use commands:
61+
62+
- `start`
63+
- `stop`
64+
- `stats`
65+
- `status`
66+
- `reset-stats`
67+
- `export exported.csv`
68+
- `quit`
69+
70+
### Timed run (auto start/stop)
71+
72+
```bash
73+
./build/device_sensor_logger --mode sim --output sqlite --file sensor_log.db --duration-sec 20 --max-samples 50 --export out.csv
74+
```
75+
76+
### Serial mode example (Linux)
77+
78+
```bash
79+
./build/device_sensor_logger --mode serial --serial-port /dev/ttyUSB0 --baud 115200 --output csv --file serial_log.csv
80+
```
81+
82+
Expected incoming serial line format:
83+
84+
```text
85+
24.1,315.5
86+
```
87+
88+
## Command Line Options
89+
90+
- `--mode sim|serial`
91+
- `--serial-port <path>`
92+
- `--baud <rate>`
93+
- `--interval-ms <milliseconds>`
94+
- `--output csv|sqlite`
95+
- `--file <output-path>`
96+
- `--duration-sec <seconds>`
97+
- `--max-samples <count>`
98+
- `--temp-alert-min <value>`
99+
- `--temp-alert-max <value>`
100+
- `--light-alert-min <value>`
101+
- `--light-alert-max <value>`
102+
- `--export <csv-path>`
103+
104+
## Example Output
105+
106+
```text
107+
Sensor Data Logger
108+
Commands:
109+
start Start logging
110+
stop Stop logging
111+
stats Show min/max/avg
112+
status Show runtime counters and last reading
113+
reset-stats Clear collected session statistics
114+
export <file.csv> Export log data to CSV
115+
help Show commands
116+
quit Stop and exit
117+
> start
118+
Logging started.
119+
> status
120+
running=yes accepted=5 rejected=0 read_failures=0 write_failures=0 alerts=1
121+
last=2026-03-12T12:00:05Z temp=28.214C light=92.113 lux
122+
```
123+
124+
## Notes
125+
126+
- If SQLite3 is not found during CMake configure, SQLite output is disabled automatically.
127+
- In serial mode on Linux, ensure your user has permission to access the serial device (for example, group `dialout`).
128+
- On Windows, serial mode is stubbed out in the current implementation and simulated mode should be used unless Windows serial support is added.

include/DataLogger.h

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#pragma once
2+
3+
#include "LogSink.h"
4+
#include "Sensor.h"
5+
#include "Stats.h"
6+
7+
#include <atomic>
8+
#include <chrono>
9+
#include <memory>
10+
#include <mutex>
11+
#include <thread>
12+
13+
struct AlertThresholds {
14+
bool hasMinTemperatureC = false;
15+
double minTemperatureC = 0.0;
16+
bool hasMaxTemperatureC = false;
17+
double maxTemperatureC = 0.0;
18+
bool hasMinLightLux = false;
19+
double minLightLux = 0.0;
20+
bool hasMaxLightLux = false;
21+
double maxLightLux = 0.0;
22+
};
23+
24+
class DataLogger {
25+
public:
26+
DataLogger(std::unique_ptr<ISensor> sensor,
27+
std::unique_ptr<ILogSink> sink,
28+
std::chrono::milliseconds interval,
29+
AlertThresholds thresholds = {},
30+
std::size_t maxSamples = 0);
31+
~DataLogger();
32+
33+
bool start();
34+
void stop();
35+
bool isRunning() const;
36+
37+
LoggerStats stats() const;
38+
LoggerRuntimeSummary summary() const;
39+
bool exportLogs(const std::string& outputPath);
40+
void resetStats();
41+
42+
private:
43+
void loop();
44+
bool isReadingValid(const SensorReading& reading) const;
45+
bool isAlert(const SensorReading& reading) const;
46+
47+
std::unique_ptr<ISensor> sensor_;
48+
std::unique_ptr<ILogSink> sink_;
49+
std::chrono::milliseconds interval_;
50+
AlertThresholds thresholds_;
51+
std::size_t maxSamples_;
52+
53+
std::atomic<bool> running_;
54+
std::thread worker_;
55+
56+
mutable std::mutex statsMutex_;
57+
StatsAggregator stats_;
58+
std::size_t acceptedSamples_;
59+
std::size_t rejectedSamples_;
60+
std::size_t readFailures_;
61+
std::size_t writeFailures_;
62+
std::size_t alertCount_;
63+
bool hasLastReading_;
64+
SensorReading lastReading_;
65+
};

include/LogSink.h

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#pragma once
2+
3+
#include "Reading.h"
4+
5+
#include <fstream>
6+
#include <memory>
7+
#include <string>
8+
9+
class ILogSink {
10+
public:
11+
virtual ~ILogSink() = default;
12+
virtual bool open() = 0;
13+
virtual bool write(const SensorReading& reading) = 0;
14+
virtual void close() = 0;
15+
virtual std::string name() const = 0;
16+
virtual bool exportToCsv(const std::string& outputPath) { (void)outputPath; return false; }
17+
};
18+
19+
class CsvLogSink final : public ILogSink {
20+
public:
21+
explicit CsvLogSink(std::string filePath);
22+
bool open() override;
23+
bool write(const SensorReading& reading) override;
24+
void close() override;
25+
std::string name() const override;
26+
bool exportToCsv(const std::string& outputPath) override;
27+
28+
private:
29+
std::string filePath_;
30+
std::ofstream file_;
31+
};
32+
33+
class SqliteLogSink final : public ILogSink {
34+
public:
35+
explicit SqliteLogSink(std::string dbPath);
36+
~SqliteLogSink() override;
37+
38+
bool open() override;
39+
bool write(const SensorReading& reading) override;
40+
void close() override;
41+
std::string name() const override;
42+
bool exportToCsv(const std::string& outputPath) override;
43+
44+
private:
45+
std::string dbPath_;
46+
47+
#if defined(HAS_SQLITE)
48+
struct sqlite3* db_;
49+
#else
50+
void* db_;
51+
#endif
52+
};

include/Reading.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#pragma once
2+
3+
#include <chrono>
4+
5+
struct SensorReading {
6+
std::chrono::system_clock::time_point timestamp;
7+
double temperatureC = 0.0;
8+
double lightLux = 0.0;
9+
};

include/Sensor.h

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#pragma once
2+
3+
#include "Reading.h"
4+
5+
#include <random>
6+
#include <string>
7+
8+
class ISensor {
9+
public:
10+
virtual ~ISensor() = default;
11+
virtual bool read(SensorReading& reading) = 0;
12+
virtual std::string name() const = 0;
13+
};
14+
15+
class SimulatedSensor final : public ISensor {
16+
public:
17+
SimulatedSensor();
18+
bool read(SensorReading& reading) override;
19+
std::string name() const override;
20+
21+
private:
22+
std::mt19937 rng_;
23+
std::normal_distribution<double> tempNoise_;
24+
std::normal_distribution<double> lightNoise_;
25+
double temperatureC_;
26+
double lightLux_;
27+
};
28+
29+
class SerialSensor final : public ISensor {
30+
public:
31+
SerialSensor(std::string portName, int baudRate);
32+
~SerialSensor() override;
33+
34+
bool read(SensorReading& reading) override;
35+
std::string name() const override;
36+
37+
private:
38+
std::string portName_;
39+
int baudRate_;
40+
int fd_;
41+
42+
bool openPort();
43+
void closePort();
44+
};

0 commit comments

Comments
 (0)