diff --git a/.vscode/launch.json b/.vscode/launch.json index 7e724e0..e0bf49c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -51,6 +51,30 @@ "ignoreFailures": true } ] + }, + { + // Локальний debug стартера ДЗ 5. preLaunchTask збирає debug preset. + "name": "Debug homework_05: telemetry_check", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/debug/homework_05/telemetry_check", + "args": [ + "${workspaceFolder}/homework_05/data/good.txt" + ], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "miDebuggerPath": "/usr/bin/gdb", + "preLaunchTask": "CMake: configure and build", + "setupCommands": [ + { + "description": "Увімкнути pretty-printing для STL типів у GDB.", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] } ] -} +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 79f396b..19f96e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Домашні роботи. По мірі появи додаємо кожну окремим add_subdirectory. add_subdirectory(homework_04) +add_subdirectory(homework_05) # Demo-код для заняття 2.4: локальне і віддалене відлагодження через GDB. add_subdirectory(demos/lesson_2_4/debug_probe) diff --git a/CMakePresets.json b/CMakePresets.json index f7f4f45..9fb8554 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -26,6 +26,26 @@ "CMAKE_BUILD_TYPE": "Debug", "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/cmake/toolchains/aarch64-linux-gnu.cmake" } + }, + { + "name": "release", + "displayName": "Release", + "description": "Release build без debug символів.", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/release", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "relwithdebinfo", + "displayName": "Release with Debug Info", + "description": "Release build із debug символами.", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/relwithdebinfo", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo" + } } ], "buildPresets": [ @@ -40,6 +60,18 @@ "displayName": "Build ARM64 Debug", "description": "Зібрати ARM64 debug preset після cross-configure.", "configurePreset": "aarch64-debug" + }, + { + "name": "release", + "displayName": "Build Release", + "description": "Зібрати release preset після configure.", + "configurePreset": "release" + }, + { + "name": "relwithdebinfo", + "displayName": "Build RelWithDebInfo", + "description": "Зібрати relwithdebinfo preset після configure.", + "configurePreset": "relwithdebinfo" } ] } diff --git a/homework_05/CMakeLists.txt b/homework_05/CMakeLists.txt new file mode 100644 index 0000000..3ef0de7 --- /dev/null +++ b/homework_05/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.20) +project(telemetry_check CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +add_library(telemetry STATIC src/telemetry.cpp) +target_include_directories(telemetry PUBLIC include) + +add_executable(telemetry_check src/main.cpp) +target_link_libraries(telemetry_check PRIVATE telemetry) diff --git a/homework_05/include/telemetry.hpp b/homework_05/include/telemetry.hpp index 0cdf002..5fd6d4e 100644 --- a/homework_05/include/telemetry.hpp +++ b/homework_05/include/telemetry.hpp @@ -12,6 +12,7 @@ struct Frame { double temperature_c; int gps_fix; int satellites; + bool error; }; // Aggregated values printed by the executable. diff --git a/homework_05/src/main.cpp b/homework_05/src/main.cpp index 43ec461..b284e0b 100644 --- a/homework_05/src/main.cpp +++ b/homework_05/src/main.cpp @@ -12,6 +12,10 @@ int main(int argc, char** argv) { Frame frames[MAX_TELEMETRY_FRAMES]; const int frame_count = read_frames(argv[1], frames, MAX_TELEMETRY_FRAMES); + if (frame_count == -1) { + return 1; + } + const Summary summary = summarize(frames, frame_count); print_summary(summary); diff --git a/homework_05/src/telemetry.cpp b/homework_05/src/telemetry.cpp index 9ac87fc..0749b4c 100644 --- a/homework_05/src/telemetry.cpp +++ b/homework_05/src/telemetry.cpp @@ -11,6 +11,7 @@ const int EXPECTED_FIELD_COUNT = 7; const int MAX_LINE_LENGTH = 256; +const int TEMP_RANGE[] = {-40, 120}; int split_line(char line[], char* fields[], int max_fields) { int count = 0; @@ -43,7 +44,7 @@ long parse_long(const char* text) { const long value = std::strtol(text, &end, 10); if (end == text) { - std::abort(); + throw std::invalid_argument("invalid long value: \"" + std::string(text) + '"'); } return value; @@ -58,25 +59,41 @@ double parse_double(const char* text) { const double value = std::strtod(text, &end); if (end == text) { - std::abort(); + throw std::invalid_argument("invalid double value: \"" + std::string(text) + '"'); } return value; } -Frame parse_frame(char line[]) { +Frame parse_frame(char line[], int frame_index) { char* fields[EXPECTED_FIELD_COUNT] = {}; const int field_count = split_line(line, fields, EXPECTED_FIELD_COUNT); (void)field_count; Frame frame{}; - frame.timestamp_ms = parse_long(fields[0]); - frame.seq = parse_int(fields[1]); - frame.voltage_v = parse_double(fields[2]); - frame.current_a = parse_double(fields[3]); - frame.temperature_c = parse_double(fields[4]); - frame.gps_fix = parse_int(fields[5]); - frame.satellites = parse_int(fields[6]); + + for (int i = 0; i < EXPECTED_FIELD_COUNT; ++i) { + if (fields[i] == nullptr) { + std::cerr << "error: invalid frame at line " << frame_index + 1 << ": missing field " << std::endl; + frame.error = true; + + return frame; + } + } + + try { + frame.timestamp_ms = parse_long(fields[0]); + frame.seq = parse_int(fields[1]); + frame.voltage_v = parse_double(fields[2]); + frame.current_a = parse_double(fields[3]); + frame.temperature_c = parse_double(fields[4]); + frame.gps_fix = parse_int(fields[5]); + frame.satellites = parse_int(fields[6]); + } catch (const std::invalid_argument& e) { + std::cerr << "error: invalid frame " << frame_index + 1 << ": " << e.what() << std::endl; + frame.error = true; + } + return frame; } @@ -90,7 +107,7 @@ int read_frames(const char* path, Frame frames[], int max_frames) { std::ifstream input{path}; if (!input) { std::cerr << "error: failed to open input file: " << path << '\n'; - return 0; + return -1; } int frame_count = 0; @@ -102,11 +119,66 @@ int read_frames(const char* path, Frame frames[], int max_frames) { } if (frame_count < max_frames) { - frames[frame_count] = parse_frame(line); + frames[frame_count] = parse_frame(line, frame_count); + if (frame_count > 0) { + if (frames[frame_count].seq != frames[frame_count - 1].seq + 1) { + std::cerr << "error: invalid frame at line " << frame_count + 1 + << ": non-sequential frame index " << frames[frame_count].seq + << " after " << frames[frame_count - 1].seq << std::endl; + frames[frame_count].error = true; + } + + if (frames[frame_count].timestamp_ms <= frames[frame_count - 1].timestamp_ms) { + std::cerr << "error: invalid frame at line " << frame_count + 1 + << ": non-monotonic timestamp " << frames[frame_count].timestamp_ms + << " after " << frames[frame_count - 1].timestamp_ms << std::endl; + frames[frame_count].error = true; + } + + if (frames[frame_count].voltage_v <= 0.0) { + std::cerr << "error: invalid frame at line " << frame_count + 1 + << ": voltage must be positive, got " << frames[frame_count].voltage_v + << std::endl; + frames[frame_count].error = true; + } + + if (frames[frame_count].temperature_c < TEMP_RANGE[0] || + frames[frame_count].temperature_c > TEMP_RANGE[1]) { + std::cerr << "error: invalid frame at line " << frame_count + 1 + << ": temperature out of range [" << TEMP_RANGE[0] << ", " + << TEMP_RANGE[1] << "], got " << frames[frame_count].temperature_c + << std::endl; + frames[frame_count].error = true; + } + + if (frames[frame_count].gps_fix != 0 && frames[frame_count].gps_fix != 1) { + std::cerr << "error: invalid frame at line " << frame_count + 1 + << ": gps_fix must be in range [0, 1], got " << frames[frame_count].gps_fix + << std::endl; + frames[frame_count].error = true; + } + + if (frames[frame_count].satellites < 0) { + std::cerr << "error: invalid frame at line " << frame_count + 1 + << ": satellites must be non-negative, got " << frames[frame_count].satellites + << std::endl; + frames[frame_count].error = true; + } + } + + if (frames[frame_count].error) { + return -1; + } + ++frame_count; } } + if (frame_count == 0) { + std::cerr << "error: zero telemetery frames provided in file: " << path << std::endl; + return -1; + } + return frame_count; }