diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0119e7e1..2202123d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -30,7 +30,7 @@ jobs: - name: Build and Push Docker Image run: | - docker build -t ghcr.io/robertgawron/hardwaredatalogger:${{ github.sha }} -f DevOps/HwDev/Docker/Dockerfile . + docker build -t ghcr.io/robertgawron/hardwaredatalogger:${{ github.sha }} -f DevOps/Dockerfile . docker push ghcr.io/robertgawron/hardwaredatalogger:${{ github.sha }} # Parallel Jobs: Unit Tests, Code Coverage, etc. @@ -115,7 +115,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: code-coverage-results - path: DevOps/HwDev/BuildArtifacts/CodeCoverage + path: DevOps/BuildArtifacts/CodeCoverage - name: Clean up Docker containers run: | @@ -162,14 +162,63 @@ jobs: uses: actions/upload-artifact@v4 with: name: docs-coverage-results - path: DevOps/HwDev/BuildArtifacts/DocsCoverage + path: DevOps/BuildArtifacts/DocsCoverage - name: Clean up Docker containers run: | docker-compose -f docker-compose.yml -f docker-compose.ci.yml down - static-analysis: - name: Run Static Analysis + static-analysis-python: + name: Run Static Analysis (python) + runs-on: ubuntu-latest + needs: build + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Install Docker Compose + run: | + sudo apt-get update + sudo apt-get install -y docker-compose + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Pull Docker Image + run: | + docker pull ghcr.io/robertgawron/hardwaredatalogger:${{ github.sha }} + docker tag ghcr.io/robertgawron/hardwaredatalogger:${{ github.sha }} hardwaredatalogger:latest + + - name: Run Docker Compose + run: | + docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d + + - name: Run Static Analysis + run: | + docker-compose -f docker-compose.yml -f docker-compose.ci.yml exec -T dev bash -c "\ + set -e; \ + cd /workspace/build && cmake .. && make pystatic" + + - name: Upload Static Analysis (Python) Results + if: always() + uses: actions/upload-artifact@v4 + with: + name: static-analysis-results-python + path: DevOps/BuildArtifacts/PythonStaticAnalysis + + - name: Clean up Docker containers + run: | + docker-compose -f docker-compose.yml -f docker-compose.ci.yml down + + static-analysis-c: + name: Run Static Analysis (cpp) runs-on: ubuntu-latest needs: build steps: @@ -210,9 +259,9 @@ jobs: if: always() uses: actions/upload-artifact@v4 with: - name: static-analysis-results - path: DevOps/HwDev/BuildArtifacts/StaticAnalysis + name: static-analysis-results-cpp + path: DevOps/BuildArtifacts/StaticAnalysis - name: Clean up Docker containers run: | - docker-compose -f docker-compose.yml -f docker-compose.ci.yml down + docker-compose -f docker-compose.yml -f docker-compose.ci.yml down \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 077d73f3..c412bdd9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,10 @@ add_subdirectory(Software/STM32F103RBTx/Application) add_subdirectory(Simulation/FirmwarePCSimulator) add_subdirectory(Test/Unit) -include(Software/StaticAnalysis.cmake) +include(Software/CStaticAnalysis.cmake) include(Software/ClassDiagram.cmake) include(Software/DocsCoverage.cmake) + +include(Simulation/FirmwarePCSimulator/PythonStaticAnalysis.cmake) + + diff --git a/DevOps/HwDev/Docker/Dockerfile b/DevOps/Dockerfile similarity index 98% rename from DevOps/HwDev/Docker/Dockerfile rename to DevOps/Dockerfile index f7f5a5a4..58c882c3 100644 --- a/DevOps/HwDev/Docker/Dockerfile +++ b/DevOps/Dockerfile @@ -121,7 +121,9 @@ RUN /workspace/venv/bin/pip install --upgrade pip && \ pytest \ pytest-html \ PyQt6 \ - codechecker + codechecker \ + prospector \ + prospector2html # Add virtual environment binaries to the PATH ENV PATH="/workspace/venv/bin:$PATH" diff --git a/DevOps/PiDev/.keep b/DevOps/PiDev/.keep deleted file mode 100644 index e69de29b..00000000 diff --git a/DevOps/PiProd/.keep b/DevOps/PiProd/.keep deleted file mode 100644 index e69de29b..00000000 diff --git a/DevOps/README.md b/DevOps/README.md index 6523171f..88bbf087 100644 --- a/DevOps/README.md +++ b/DevOps/README.md @@ -1,118 +1 @@ -# Docker - -## Why? -For me, using Docker in embedded systems has a key advantage: it eliminates the need for a README filled with outdated commands for setting up the development environment. In reality, such lists are rarely kept up to date (I often forget to update them myself too!). Docker solves this problem by providing a structured list of everything you need to install to get your environment up and running. But it's more than just a list - it's code, meaning it can be verified, maintained, and reused easily. - -## Prerequisites - -* Install Docker. For Linux it should be simple, for Windows you need WSL installed first and some Linux image for it. - -## Build Docker image - -The idea is that we we will build image once and mount the project files, so that if there is no need to rebuild contain,er unless you add other tools. - -### Run the Docker image - -In the main directory (HardwareDataLogger) run the command to build the image. - -Build the docker image: - -docker-compose build dev - -Start the Docker image: - -docker-compose up -d --remove-orphans - -Log into the Docker image: - -docker-compose exec dev bash - -Additionally at the end of work: - -docker-compose down --remove-orphans - -## Runing stuff - -## Builds the firmware - -make code - -## Run tests - -make test - -## Run static analysis - -cd /workspace/build && cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_CXX_STANDARD=17 .. && make static - -# Run code coverage - -cmake -DCMAKE_BUILD_TYPE=Debug .. && make -j23 && make test -j23 && make coverage - -# Run docs coverage - -make docs - -# Run UML generation - -make uml - -## Running Include what you use - -cd build - -for INCLUDE_WHAT_YOU_USE: - -CC="clang-10" CXX="clang++-10" cmake -DCMAKE_CXX_INCLUDE_WHAT_YOU_USE=include-what-you-use ../Software - -for all the rest: - -cmake .. - - -# Building and running Firmware PC Simulation - -* Launch Docker as explained in the previous steps. -* Build the firmware in the PC variant: - -cd /workspace/build/ && cmake .. && make -j24 - -* Start the simulation: - -cd /workspace/Simulation/FirmwarePCSimulator/ && python3 main.py - -* all at once: - -cd /workspace/build/ && cmake .. && make -j24 && cd /workspace/Simulation/FirmwarePCSimulator/ && python3 main.py - - -### Troubleshooting: -The simulation is a desktop application that runs in Docker. Docker needs to be configured to display the simulation window. To test this, you can temporarily install some X11 applications and run them: - -apt-get update && apt-get install -y x11-apps -xclock - -If a small window with a clock is visible, it means everything is set up correctly. - -Note: If using Windows, [MobaXterm](https://mobaxterm.mobatek.net/download-home-edition.html) (or another tool capable of displaying X11 windows) is required. The Visual Studio terminal will not work. - -# Pytest - -cd /workspace/build/ && cmake .. && make -j24 - -cd /workspace/Test/System -python3 -m venv /workspace/venv -pytest test_display.py -s --html=report.html - - -after: -cd /workspace/build/ && cmake .. && make -j24 && cd /workspace/Test/System && pytest test_display.py -s --html=report.html - - - -cd /workspace/build/ && cmake .. && make -j24 && cd /workspace/Test/System && pytest test_pulse_counter.py -s --html=report.html - - - - - +[Setup Manual](../Documentation/Manuals/SetupDockerContainers.md) diff --git a/Documentation/Manuals/SetupDockerContainers.md b/Documentation/Manuals/SetupDockerContainers.md new file mode 100644 index 00000000..366840cb --- /dev/null +++ b/Documentation/Manuals/SetupDockerContainers.md @@ -0,0 +1,38 @@ +## Why? + +For me, using Docker in embedded systems has a key advantage: it eliminates the need for a README filled with outdated commands for setting up the development environment. In reality, such lists are rarely kept up to date (I often forget to update them myself too!). Docker solves this problem by providing a structured list of everything you need to install to get your environment up and running. But it's more than just a list - it's code, meaning it can be verified, maintained, and reused easily. + +## Prerequisites + +* Install Docker. For Linux, this should be straightforward. For Windows, you need to install WSL first and set up a Linux image. + +## Docker Image for Firmware Development + +### Build the Image + +In the main directory (HardwareDataLogger), run the following command to build the image: + +``` +docker-compose build dev +``` + +### Running the Docker Image + +Start the Docker image: + +``` +docker-compose up -d --remove-orphans +``` + +Log into the Docker image: + +``` +docker-compose exec dev bash +``` + +When finished, shut down the Docker container: + +``` +docker-compose down --remove-orphans +``` + diff --git a/Documentation/Manuals/SetupFirmwareDevelopment.md b/Documentation/Manuals/SetupFirmwareDevelopment.md new file mode 100644 index 00000000..8ffb5e26 --- /dev/null +++ b/Documentation/Manuals/SetupFirmwareDevelopment.md @@ -0,0 +1,50 @@ +## Prerequisites + +[Install Docker and log into the container.](./SetupDockerContainers.md) + +## Run Unit Tests + +``` +cmake .. +make test +``` + +## Run Static Analysis + +``` +cd /workspace/build && \ +cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_CXX_STANDARD=17 .. && \ +make static +``` + +## Run Code Coverage + +``` +cmake -DCMAKE_BUILD_TYPE=Debug .. && \ +make -j23 && \ +make test -j23 && \ +make coverage +``` + +## Run Docs Coverage + +``` +cmake .. +make docs +``` + +## Run UML Generation + +``` +cmake .. +make uml +``` + +## Running "Include What You Use" + +``` +cd build + +CC="clang-10" CXX="clang++-10" cmake -DCMAKE_CXX_INCLUDE_WHAT_YOU_USE=include-what-you-use ../Software +``` + diff --git a/Documentation/Manuals/SetupFirmwareSimulation.md b/Documentation/Manuals/SetupFirmwareSimulation.md new file mode 100644 index 00000000..462d712f --- /dev/null +++ b/Documentation/Manuals/SetupFirmwareSimulation.md @@ -0,0 +1,68 @@ +## Prerequisites + +* [Install Docker and log into the container.](./SetupDockerContainers.md) +* Install CubeMX +* Install VisualStudioCode +* Generate the project in STM32CubeMX, which will create the CMake setup. +* Configure the STM32 VS Code Extension. + +# Building and Running Firmware PC Simulation + +### Build the Firmware (PC Variant) + +``` +cd /workspace/build/ && cmake .. && make -j24 +``` + +### Start the Simulation + +``` +cd /workspace/Simulation/FirmwarePCSimulator/ && python3 main.py +``` + +### All-in-One Command + +``` +cd /workspace/build/ && cmake .. && make -j24 && cd /workspace/Simulation/FirmwarePCSimulator/ && python3 main.py +``` + +### Troubleshooting tunneling GUI to host enviroment + +The simulation is a desktop application that runs in Docker. Docker needs to be configured to display the simulation window. To test this, you can temporarily install some X11 applications and run them: + +``` +apt-get update && apt-get install -y x11-apps +xclock +``` + +If a small window with a clock is visible, it means everything is set up correctly. + +**Note:** If using Windows, [MobaXterm](https://mobaxterm.mobatek.net/download-home-edition.html) (or another tool capable of displaying X11 windows) is required. The Visual Studio terminal will not work. + +## Troubleshooting core dumps + +### Check if Core Dumps Are Enabled + +Core dumps might be disabled by default on your system. Check the current core dump settings using: + +``` +ulimit -c +``` + +If it shows `0`, core dumps are disabled. Enable core dumps by running: + +``` +ulimit -c unlimited +``` + +On WSL (Docker runs in Windows), disable `wsl-capture-crash` for Core Dumps: + +``` +echo "/tmp/core.%e.%p" | tee /proc/sys/kernel/core_pattern +``` + +### Debug Core Dumps + +``` +gdb /workspace/venv/bin/python3 /tmp/core.python3.2430 +``` \ No newline at end of file diff --git a/Documentation/Manuals/SetupSystemTest.md b/Documentation/Manuals/SetupSystemTest.md new file mode 100644 index 00000000..21f2c162 --- /dev/null +++ b/Documentation/Manuals/SetupSystemTest.md @@ -0,0 +1,8 @@ +## Prerequisites + +[Install Docker and log into the container.](./SetupDockerContainers.md) + +# Running tests + +cd /workspace/build/ && cmake .. && make -j24 && cd /workspace/Test/System && pytest test_display.py -s --html=report.html + diff --git a/Simulation/FirmwarePCSimulator/PythonStaticAnalysis.cmake b/Simulation/FirmwarePCSimulator/PythonStaticAnalysis.cmake new file mode 100644 index 00000000..0c3acb8e --- /dev/null +++ b/Simulation/FirmwarePCSimulator/PythonStaticAnalysis.cmake @@ -0,0 +1,20 @@ + +# Define the output directories for Python static analysis and reports +set(PYTHON_ANALYZE_DIR ${CMAKE_BINARY_DIR}/BuildArtifacts/PythonStaticAnalysis) +set(PYTHON_REPORT_DIR ${CMAKE_BINARY_DIR}/BuildArtifacts) + +# Echo the variables for debugging +message(STATUS "PYTHON_ANALYZE_DIR: ${PYTHON_ANALYZE_DIR}") +message(STATUS "PYTHON_REPORT_DIR: ${PYTHON_REPORT_DIR}") + +# Ensure the output directories exist +file(MAKE_DIRECTORY ${PYTHON_ANALYZE_DIR}) +file(MAKE_DIRECTORY ${PYTHON_REPORT_DIR}) + +# Add a custom target for Python static analysis +add_custom_target(pystatic + COMMAND bash -c " \ + source /workspace/Simulation/FirmwarePCSimulator/PythonStaticAnalysis.sh" + COMMENT "Running Python static analysis with Prospector and generating reports..." + VERBATIM +) diff --git a/Simulation/FirmwarePCSimulator/PythonStaticAnalysis.sh b/Simulation/FirmwarePCSimulator/PythonStaticAnalysis.sh new file mode 100644 index 00000000..aa2d1ea5 --- /dev/null +++ b/Simulation/FirmwarePCSimulator/PythonStaticAnalysis.sh @@ -0,0 +1,26 @@ +# Activate the virtual environment +source /workspace/venv/bin/activate + +# Set up directories for artifacts +OUTPUT_DIR="/workspace/build/BuildArtifacts/PythonStaticAnalysis" +mkdir -p "$OUTPUT_DIR" + +# Define input and output file paths +SYSTEM_JSON="$OUTPUT_DIR/SystemTests.json" +SYSTEM_HTML="$OUTPUT_DIR/SystemTests.html" +SIMULATOR_JSON="$OUTPUT_DIR/FirmwarePCSimulator.json" +SIMULATOR_HTML="$OUTPUT_DIR/FirmwarePCSimulator.html" + +# Initialize exit code tracker +EXIT_CODE=0 + +# Run Prospector analysis +prospector --output-format json /workspace/Test/System/ > "$SYSTEM_JSON" || EXIT_CODE=1 +prospector --output-format json /workspace/Simulation/FirmwarePCSimulator/ > "$SIMULATOR_JSON" || EXIT_CODE=1 + +# Convert JSON reports to HTML using prospector-html +prospector-html -i "$SYSTEM_JSON" -o "$SYSTEM_HTML" || echo "Warning: HTML conversion failed for SystemTests" +prospector-html -i "$SIMULATOR_JSON" -o "$SIMULATOR_HTML" || echo "Warning: HTML conversion failed for FirmwarePCSimulator" + +# Exit with the appropriate code +exit $EXIT_CODE diff --git a/Simulation/FirmwarePCSimulator/README.md b/Simulation/FirmwarePCSimulator/README.md index 4a8f4056..82e2c3b2 100644 --- a/Simulation/FirmwarePCSimulator/README.md +++ b/Simulation/FirmwarePCSimulator/README.md @@ -33,26 +33,6 @@ main_window.py: Main window of the application. * cmake * Docker -## Compilation and usage +## Setup -[This part is covered in the DevOps section.](../../DevOps/README.md). - -cd /workspace/build/ && cmake .. && make -j24 && cd /workspace/Simulation/FirmwarePCSimulator/ && python3 main.py - -## Troubleshooting - -Check if Core Dumps Are Enabled Core dumps might be disabled by default on your system. Check the current core dump settings using: - -ulimit -c - -If it shows 0, core dumps are disabled, enable core dumps by running: - -ulimit -c unlimited - -On WSL (Docker runs in Windows), disable wsl-capture-crash for Core Dumps: - -echo "/tmp/core.%e.%p" | tee /proc/sys/kernel/core_pattern - - -debug core: -gdb /workspace/venv/bin/python3 /tmp/core.python3.2430 +[Setup Manual](../Documentation/Manuals/SetupFirmwareSimulation.md) diff --git a/Simulation/FirmwarePCSimulator/data_visualization_widget.py b/Simulation/FirmwarePCSimulator/data_visualization_widget.py index 086514dc..af70b478 100644 --- a/Simulation/FirmwarePCSimulator/data_visualization_widget.py +++ b/Simulation/FirmwarePCSimulator/data_visualization_widget.py @@ -24,16 +24,16 @@ def __init__(self): self.setLayout(self.layout) - def update_pulse_counters(self, timestamp, values): + def update_pulse_counters(self, timestamp, new_values): """ Update the graph with new pulse counter values. :param timestamp: The timestamp of the update. - :param values: A list of pulse counter values. + :param new_values: A list of pulse counter values. """ # Append new data to the storage self.time_stamps.append(timestamp) - for i, value in enumerate(values): + for i, value in enumerate(new_values): self.pulse_counters[i].append(value) # Ensure the time series does not grow indefinitely (optional) @@ -45,8 +45,8 @@ def update_pulse_counters(self, timestamp, values): # Clear and plot new data self.ax.clear() - for i, values in self.pulse_counters.items(): - self.ax.plot(self.time_stamps, values, label=f"Pulse Counter {i + 1}") + for i, counter_values in self.pulse_counters.items(): + self.ax.plot(self.time_stamps, counter_values, label=f"Pulse Counter {i + 1}") self.ax.set_title("Pulse Counter Values Over Time") self.ax.set_xlabel("Time") diff --git a/Simulation/FirmwarePCSimulator/main.py b/Simulation/FirmwarePCSimulator/main.py index 252ee651..aec87a8d 100644 --- a/Simulation/FirmwarePCSimulator/main.py +++ b/Simulation/FirmwarePCSimulator/main.py @@ -3,6 +3,7 @@ from main_window import MainWindow from simulation import Simulation + def main() -> None: """ Entry point for the application. Initializes the PyQt application, @@ -19,5 +20,6 @@ def main() -> None: # Start the application's event loop sys.exit(app.exec()) + if __name__ == "__main__": main() diff --git a/Simulation/FirmwarePCSimulator/main_window.py b/Simulation/FirmwarePCSimulator/main_window.py index 4cb85d63..f551df5b 100644 --- a/Simulation/FirmwarePCSimulator/main_window.py +++ b/Simulation/FirmwarePCSimulator/main_window.py @@ -94,9 +94,9 @@ def on_measurement_update(self, values): Handle updates from the MeasurementDataInputWidget sliders. :param values: List of current slider values. """ - timestamp = "{:%Y-%m-%d %H:%M:%S}".format(datetime.now()) - message = "Simulated pulse counter values: " + ", ".join(f"{value}" for value in values) + timestamp = f"{datetime.now():%Y-%m-%d %H:%M:%S}" + message = f"Simulated pulse counter values: {', '.join(str(value) for value in values)}" self.log_tabs.add_log(LogTabID.PULSE_COUNTER, timestamp, message) - + self.data_visualization_tab.update_pulse_counters(timestamp, values) self.simulation.update_pulse_counters(values) diff --git a/Software/StaticAnalysis.cmake b/Software/CStaticAnalysis.cmake similarity index 97% rename from Software/StaticAnalysis.cmake rename to Software/CStaticAnalysis.cmake index 2066f42f..e8d1665f 100644 --- a/Software/StaticAnalysis.cmake +++ b/Software/CStaticAnalysis.cmake @@ -44,6 +44,7 @@ add_custom_target(static --disable clang-diagnostic-weak-vtables --disable altera-id-dependent-backward-branch --disable bugprone-easily-swappable-parameters + --disable clang-diagnostic-covered-switch-default COMMENT "Running CodeChecker analysis..." VERBATIM ) diff --git a/Software/ESP8266MOD/ESP8266/.gitignore b/Software/ESP8266MOD/ESP8266/.gitignore new file mode 100644 index 00000000..89cc49cb --- /dev/null +++ b/Software/ESP8266MOD/ESP8266/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/Software/ESP8266MOD/ESP8266/include/README b/Software/ESP8266MOD/ESP8266/include/README new file mode 100644 index 00000000..194dcd43 --- /dev/null +++ b/Software/ESP8266MOD/ESP8266/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/Software/ESP8266MOD/ESP8266/lib/README b/Software/ESP8266MOD/ESP8266/lib/README new file mode 100644 index 00000000..2593a33f --- /dev/null +++ b/Software/ESP8266MOD/ESP8266/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/Software/ESP8266MOD/ESP8266/platformio.ini b/Software/ESP8266MOD/ESP8266/platformio.ini new file mode 100644 index 00000000..cc59f660 --- /dev/null +++ b/Software/ESP8266MOD/ESP8266/platformio.ini @@ -0,0 +1,18 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:huzzah] +platform = espressif8266 +board = huzzah +framework = arduino +upload_port = COM3 +upload_speed = 115200 ; Typical baud rate for ESP8266 +monitor_port = COM3 +monitor_speed = 115200 ; Baud rate for serial monitor \ No newline at end of file diff --git a/Software/ESP8266MOD/ESP8266/src/main.cpp b/Software/ESP8266MOD/ESP8266/src/main.cpp new file mode 100644 index 00000000..ea48d153 --- /dev/null +++ b/Software/ESP8266MOD/ESP8266/src/main.cpp @@ -0,0 +1,31 @@ +#include // Include the ESP8266 WiFi library (not strictly necessary for serial communication, but typically included for ESP8266 projects) + +unsigned long previousMillis = 0; // Stores the last time the message was sent +const long interval = 2000; // Interval at which to send the message (in milliseconds) + +int USR_LED_GPIO = 13; + +void setup() +{ + pinMode(USR_LED_GPIO, OUTPUT); + Serial.begin(115200); // Start the serial communication at 115200 baud rate +} +void loop() +{ + if (Serial.available() > 0) + { // Check if data is available to read from the serial port + char receivedChar = Serial.read(); // Read the incoming data + if (receivedChar == '\n') + { + + // Blink LED when data is received + digitalWrite(USR_LED_GPIO, HIGH); // Turn the LED on + delay(500); // Wait for 500 milliseconds + digitalWrite(USR_LED_GPIO, LOW); // Turn the LED off + delay(500); // Wait for 500 milliseconds + + // Optionally, echo back the received data to the STM32 + Serial.print("receivedData"); + } + } +} diff --git a/Software/ESP8266MOD/ESP8266/test/README b/Software/ESP8266MOD/ESP8266/test/README new file mode 100644 index 00000000..9b1e87bc --- /dev/null +++ b/Software/ESP8266MOD/ESP8266/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html diff --git a/Software/ESP8266MOD/ESP8266MOD.ino b/Software/ESP8266MOD/ESP8266MOD.ino deleted file mode 100644 index cf3e067a..00000000 --- a/Software/ESP8266MOD/ESP8266MOD.ino +++ /dev/null @@ -1,48 +0,0 @@ -#include // Include the ESP8266 WiFi library (not strictly necessary for serial communication, but typically included for ESP8266 projects) - -unsigned long previousMillis = 0; // Stores the last time the message was sent -const long interval = 2000; // Interval at which to send the message (in milliseconds) - - -int USR_LED_GPIO = 13; - -void setup() -{ - pinMode (USR_LED_GPIO, OUTPUT); - Serial.begin(115200); // Start the serial communication at 115200 baud rate - -} -void loop() -{ - /* - digitalWrite(USR_LED_GPIO, HIGH); - delay(1000); - digitalWrite(USR_LED_GPIO, LOW); - delay(1000); - -// unsigned long currentMillis = millis(); - - // Check if the interval has elapsed - //if (currentMillis - previousMillis >= interval) { - // previousMillis = currentMillis; // Save the last time a message was sent - - Serial.println("abcde\r\n"); // Send the message - // } - */ - - if (Serial.available() > 0) { // Check if data is available to read from the serial port - char receivedChar = Serial.read(); // Read the incoming data - if (receivedChar == '\n') - { - - // Blink LED when data is received - digitalWrite(USR_LED_GPIO, HIGH); // Turn the LED on - delay(500); // Wait for 500 milliseconds - digitalWrite(USR_LED_GPIO, LOW); // Turn the LED off - delay(500); // Wait for 500 milliseconds - - // Optionally, echo back the received data to the STM32 - Serial.print("receivedData"); - } - } -} diff --git a/Software/ESP8266MOD/README.md b/Software/ESP8266MOD/README.md deleted file mode 100644 index 1e6debb5..00000000 --- a/Software/ESP8266MOD/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# ESP8266MOD (WiFi interface) - -### Tools -TODO diff --git a/Software/STM32F103RBTx/Application/BusinessLogic/Src/HmiMuiHandlers.cpp b/Software/STM32F103RBTx/Application/BusinessLogic/Src/HmiMuiHandlers.cpp index ee902367..86188c62 100644 --- a/Software/STM32F103RBTx/Application/BusinessLogic/Src/HmiMuiHandlers.cpp +++ b/Software/STM32F103RBTx/Application/BusinessLogic/Src/HmiMuiHandlers.cpp @@ -28,14 +28,12 @@ namespace BusinessLogic namespace { - // Define a constant for maximum displays, change if needed constexpr std::size_t MAX_MUI_AMOUNT = 1u; std::array muiMap{}; constexpr std::size_t LabelTextBufferSize = 10; char labelTextBuffer[LabelTextBufferSize]; - } uint8_t device1_printLastReading(mui_t *muiHandler, uint8_t muiMessage) @@ -75,6 +73,8 @@ namespace BusinessLogic DisplayMapEntry *findEntryByMui(mui_t *muiHandler) { + // The std::find_if function returns an iterator, which is an object (not a raw pointer) in modern STL containers. + // codechecker_suppress [readability-qualified-auto] auto it = std::find_if( muiMap.begin(), muiMap.end(), diff --git a/Software/STM32F103RBTx/Application/Device/Inc/Display.hpp b/Software/STM32F103RBTx/Application/Device/Inc/Display.hpp index 7e222eb0..d1434235 100644 --- a/Software/STM32F103RBTx/Application/Device/Inc/Display.hpp +++ b/Software/STM32F103RBTx/Application/Device/Inc/Display.hpp @@ -89,12 +89,12 @@ namespace Device * This method sets up the `u8g2` library with the appropriate callbacks and parameters * for the ST7735 display hardware. * - * @param u8g2 Pointer to the `u8g2_t` structure representing the library context. + * @param u8g2Handler Pointer to the `u8g2_t` structure representing the library context. * @param rotation Pointer to a `u8g2_cb_t` structure specifying the display rotation. * @param byte_cb Byte callback function for communication with the display. * @param gpio_and_delay_cb GPIO and delay callback function for controlling hardware signals. */ - static void u8g2_Setup_st7735(u8g2_t *u8g2, const u8g2_cb_t *rotation, u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb); + static void u8g2_Setup_st7735(u8g2_t *u8g2Handler, const u8g2_cb_t *rotation, u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb); /// Reference to the display driver used for managing display operations. Driver::IDisplayDriver &displayDriver; diff --git a/Software/STM32F103RBTx/Application/Device/Src/Display.cpp b/Software/STM32F103RBTx/Application/Device/Src/Display.cpp index b6cdc4f9..b78791d6 100644 --- a/Software/STM32F103RBTx/Application/Device/Src/Display.cpp +++ b/Software/STM32F103RBTx/Application/Device/Src/Display.cpp @@ -124,50 +124,53 @@ namespace Device case U8X8_MSG_DISPLAY_DRAW_TILE: { - const u8x8_tile_t *tile = static_cast(argPtr); + const u8x8_tile_t *tiles = static_cast(argPtr); for (std::uint8_t t = 0; t < argInt; t++) { - const std::uint8_t tile_x_start = tile->x_pos * TILE_PIXEL_HEIGHT; // Starting x position in pixels - const std::uint8_t tile_y_start = tile->y_pos * TILE_PIXEL_HEIGHT; // Starting y position in pixels + // lib api, can't change it + // codechecker_suppress [clang-diagnostic-unsafe-buffer-usage] + const u8x8_tile_t &tile = tiles[t]; // Access the current tile safely using an index - // printf("tile_x_start x=%d, y=%d\n", tile_x_start, tile_y_start); + const std::uint8_t tile_x_start = tile.x_pos * TILE_PIXEL_HEIGHT; // Starting x position in pixels + const std::uint8_t tile_y_start = tile.y_pos * TILE_PIXEL_HEIGHT; // Starting y position in pixels // Treat each byte as a column of 8 vertical pixels. - for (std::uint8_t col = 0; col < 120; col++) + constexpr std::uint8_t len = 120; // todo think about it + for (std::uint8_t col = 0; col < len; col++) { - const std::uint8_t col_data = tile->tile_ptr[col]; // This is a vertical column + // lib api, can't change it + // codechecker_suppress [clang-diagnostic-unsafe-buffer-usage] + const std::uint8_t col_data = tile.tile_ptr[col]; // This is a vertical column for (std::uint8_t bit = 0; bit < TILE_PIXEL_HEIGHT; bit++) { Driver::DisplayPixelColor::PixelColor color = Driver::DisplayPixelColor::getColor(0x00, 0x00, 0x00); const std::uint8_t x = tile_x_start + col; const std::uint8_t y = tile_y_start + bit; - if (col_data & (1 << bit)) + if ((col_data & (1 << bit)) != 0) { // Now (col, bit) corresponds to (x, y) pixel offsets within the tile: - // x = tile_x_start + col - // y = tile_y_start + bit - // printf("Pixel ON at x=%d, y=%d\n", tile_x_start + col, tile_y_start + bit); color = Driver::DisplayPixelColor::getColor(DEFAULT_COLOR_RED, DEFAULT_COLOR_GREEN, DEFAULT_COLOR_BLUE); } displayDriver.setPixel(x, y, color); } } - - tile++; } } - break; - case U8X8_MSG_DISPLAY_SET_POWER_SAVE: - case U8X8_MSG_DISPLAY_SET_FLIP_MODE: - case U8X8_MSG_DISPLAY_REFRESH: - { - } break; + /* + case U8X8_MSG_DISPLAY_SET_POWER_SAVE: + case U8X8_MSG_DISPLAY_SET_FLIP_MODE: + case U8X8_MSG_DISPLAY_REFRESH: + { + } + break; + */ + default: { // printf("Unhandled message: %d\n", msg); @@ -178,7 +181,7 @@ namespace Device return U8G2_STATUS_OK; } - void Display::u8g2_Setup_st7735(u8g2_t *u8g2, const u8g2_cb_t *rotation, u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb) + void Display::u8g2_Setup_st7735(u8g2_t *u8g2Handler, const u8g2_cb_t *rotation, u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb) { // Calculate the number of tile rows needed for 128x128 resolution. // Each tile is 8 pixels high, so for a 128-pixel height: @@ -188,7 +191,9 @@ namespace Device // Each tile is 8 pixels wide * 1 byte (8 bits) = 8 bytes per tile. // For 16 rows and 16 tiles per row: // Total buffer size = 16 rows * 16 tiles/row * 8 bytes/tile = 2048 bytes. - std::uint8_t *buf = static_cast(malloc(128 * tile_buf_height)); + constexpr std::size_t len = 128; + std::uint8_t *buf = static_cast(malloc( + len * static_cast(tile_buf_height))); if (buf == nullptr) { @@ -198,11 +203,11 @@ namespace Device // Setup the display with the appropriate parameters. // Note that this is u8g2 macro so I cant modify it to avoid warning - // codechecker_suppress [mold-style-cast, cppcheck-cstyleCast] - u8g2_SetupDisplay(u8g2, trampolineU8x8DSt7735, u8x8_cad_001, byte_cb, gpio_and_delay_cb); + // codechecker_suppress [mold-style-cast, cppcheck-cstyleCast, clang-diagnostic-old-style-cast] + u8g2_SetupDisplay(u8g2Handler, trampolineU8x8DSt7735, u8x8_cad_001, byte_cb, gpio_and_delay_cb); // Configure the buffer and the rendering method (vertical top to bottom). - u8g2_SetupBuffer(u8g2, buf, tile_buf_height, u8g2_ll_hvline_vertical_top_lsb, rotation); + u8g2_SetupBuffer(u8g2Handler, buf, tile_buf_height, u8g2_ll_hvline_vertical_top_lsb, rotation); } Display::Display(Driver::IDisplayDriver &_displayDriver) : displayDriver(_displayDriver) diff --git a/Software/STM32F103RBTx/Application/Device/Src/Keyboard.cpp b/Software/STM32F103RBTx/Application/Device/Src/Keyboard.cpp index 3c890ce0..a3a8b0bb 100644 --- a/Software/STM32F103RBTx/Application/Device/Src/Keyboard.cpp +++ b/Software/STM32F103RBTx/Application/Device/Src/Keyboard.cpp @@ -82,6 +82,10 @@ namespace Device // default: // If there are other states or error conditions, handle them here break; + default: + { + } + break; } keyActionState[i] = nextState; diff --git a/Software/STM32F103RBTx/Application/Device/Src/WiFiMeasurementRecorder.cpp b/Software/STM32F103RBTx/Application/Device/Src/WiFiMeasurementRecorder.cpp index 534ece5a..ccf7e6f5 100644 --- a/Software/STM32F103RBTx/Application/Device/Src/WiFiMeasurementRecorder.cpp +++ b/Software/STM32F103RBTx/Application/Device/Src/WiFiMeasurementRecorder.cpp @@ -103,7 +103,7 @@ namespace Device const Driver::UartExchangeStatus driverStatus = driver.transmit( data.data(), - currentDataPosition, + static_cast(currentDataPosition), Driver::IUartDriver::MaxDelay); status = (driverStatus == Driver::UartExchangeStatus::Ok); diff --git a/Software/STM32F103RBTx/Application/Driver/Src/KeyboardFourPushButtonsDriver.cpp b/Software/STM32F103RBTx/Application/Driver/Src/KeyboardFourPushButtonsDriver.cpp index 4f4e77e9..02496371 100644 --- a/Software/STM32F103RBTx/Application/Driver/Src/KeyboardFourPushButtonsDriver.cpp +++ b/Software/STM32F103RBTx/Application/Driver/Src/KeyboardFourPushButtonsDriver.cpp @@ -61,7 +61,8 @@ namespace Driver if (isInState(DriverState::State::Running)) { // Use std::find_if to locate the key configuration in the array - // Use std::find_if to locate the key configuration in the array + // The std::find_if function returns an iterator, which is an object (not a raw pointer) in modern STL containers. + // codechecker_suppress [readability-qualified-auto] auto it = std::find_if( keyState.begin(), keyState.end(), diff --git a/Software/STM32F103RBTx/Application/Driver/Src/St7735DisplayBrightnessDriver.cpp b/Software/STM32F103RBTx/Application/Driver/Src/St7735DisplayBrightnessDriver.cpp index 1ee80150..34541901 100644 --- a/Software/STM32F103RBTx/Application/Driver/Src/St7735DisplayBrightnessDriver.cpp +++ b/Software/STM32F103RBTx/Application/Driver/Src/St7735DisplayBrightnessDriver.cpp @@ -71,10 +71,14 @@ namespace Driver // Enable the clock for TIM3 __HAL_RCC_TIM3_CLK_ENABLE(); + constexpr std::uint32_t TIMER_CLOCK_FREQ = 24000000; // Timer clock frequency in Hz + constexpr std::uint32_t PWM_FREQ = 5000; // Desired PWM frequency in Hz + constexpr std::uint32_t TIMER_PERIOD = (TIMER_CLOCK_FREQ / PWM_FREQ) - 1; + htim.Instance = TIM3; // This should be configurable. htim.Init.Prescaler = 0; // No prescaler, timer clock remains 24 MHz htim.Init.CounterMode = TIM_COUNTERMODE_UP; - htim.Init.Period = 4799; // Period to get a PWM frequency of 5 kHz + htim.Init.Period = TIMER_PERIOD; // Period to get a PWM frequency of 5 kHz htim.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; diff --git a/Software/STM32F103RBTx/README.md b/Software/STM32F103RBTx/README.md index cde23eed..bbeff1d1 100644 --- a/Software/STM32F103RBTx/README.md +++ b/Software/STM32F103RBTx/README.md @@ -8,11 +8,9 @@ Note: Some people argue that using STM32CubeMX is a bad idea and that everything should be coded manually. I disagree, especially for personal projects. STM32CubeMX is a great time-saver and offers a more visual way to configure the project, making it easier to understand and modify. However, the GUI can be somewhat primitive and could definitely be improve -## Building -* Generate the project in STM32CubeMX, which will create the CMake setup. -* Configure the STM32 VS Code Extension. +## Setup -Note: The generated CMake setup is not integrated with the main CMake configuration. +[Setup Manual](../../Documentation/Manuals/SetupFirmwareDevelopment.md) ## Architecture diff --git a/Test/System/README.md b/Test/System/README.md new file mode 100644 index 00000000..6e5a2cc4 --- /dev/null +++ b/Test/System/README.md @@ -0,0 +1 @@ +[Setup Manual](../../Documentation/Manuals/SetupSystemTest.md) diff --git a/Test/System/conftest.py b/Test/System/conftest.py index b4b5be96..aae8d766 100644 --- a/Test/System/conftest.py +++ b/Test/System/conftest.py @@ -4,6 +4,7 @@ from pathlib import Path from io import BytesIO from PIL import Image +from typing import Generator import pytest import pytest_html @@ -12,24 +13,24 @@ simulation_path = Path(__file__).parent.parent.parent / "Simulation" / "FirmwarePCSimulator" sys.path.append(str(simulation_path)) +# pylint: disable=import-error, wrong-import-position from simulation import Simulation logger = logging.getLogger(__name__) - @pytest.fixture -def assert_display_content(request): +def assert_display_content(request: pytest.FixtureRequest) -> callable: if not hasattr(request.node, "assert_display_contents"): request.node.assert_display_contents = [] - def generate_image(dut: Simulation): + def generate_image(dut: Simulation) -> Image.Image: """ Generate an image from the display content of the DUT. """ width = dut.get_display_width() height = dut.get_display_height() - logger.info(f"Display dimensions: width={width}, height={height}") + logger.info("Display dimensions: width=%d, height=%d", width, height) # Create a new image representing the current display state generated_image = Image.new("RGB", (width, height)) @@ -45,7 +46,8 @@ def generate_image(dut: Simulation): return generated_image - def validate_display_content(dut: Simulation, assert_name, reference_path): + # pylint: disable=unused-argument + def validate_display_content(dut: Simulation, assert_name: str, reference_path: str) -> None: """ Validate the display content by comparing the generated image with a reference. """ @@ -65,10 +67,10 @@ def validate_display_content(dut: Simulation, assert_name, reference_path): return validate_display_content - @pytest.hookimpl(hookwrapper=True) -def pytest_runtest_makereport(item, call): +def pytest_runtest_makereport(item: pytest.Item, call: pytest.CallInfo) -> Generator[None, None, None]: """Attach all assertion-related images to the HTML report.""" + # pylint: disable=unused-argument outcome = yield report = outcome.get_result() @@ -78,15 +80,14 @@ def pytest_runtest_makereport(item, call): for image_base64, assert_name in images: # Append each image to the extras list extras.append(pytest_html.extras.image(image_base64, mime_type="image/png", name=assert_name)) - + report.extras = extras - -def pytest_html_results_table_header(cells): + +def pytest_html_results_table_header(cells: list) -> None: """Add a 'Display' column to the HTML report header.""" cells.insert(2, 'Display (Actual)') - -def pytest_html_results_table_row(report, cells): +def pytest_html_results_table_row(report: pytest.TestReport, cells: list) -> None: """Add custom content (images and assertions) to the HTML report row.""" # Ensure the Display column includes all attached images if hasattr(report, "extras"): @@ -100,5 +101,3 @@ def pytest_html_results_table_row(report, cells): ) cells.insert(2, f"{images_html}") - - diff --git a/Test/System/test_display.py b/Test/System/test_display.py index 68333b5c..91d23618 100644 --- a/Test/System/test_display.py +++ b/Test/System/test_display.py @@ -7,6 +7,7 @@ simulation_path = Path(__file__).parent.parent.parent / "Simulation" / "FirmwarePCSimulator" sys.path.append(str(simulation_path)) +# pylint: disable=import-error, wrong-import-position from simulation import Simulation, SimulationKey diff --git a/Test/System/test_pulse_counter.py b/Test/System/test_pulse_counter.py index 8eb8e4fd..7e89fb25 100644 --- a/Test/System/test_pulse_counter.py +++ b/Test/System/test_pulse_counter.py @@ -7,12 +7,13 @@ simulation_path = Path(__file__).parent.parent.parent / "Simulation" / "FirmwarePCSimulator" sys.path.append(str(simulation_path)) -from simulation import Simulation, SimulationKey - +# pylint: disable=import-error, wrong-import-position +from simulation import Simulation logger = logging.getLogger(__name__) def test_pulse_counter_start(assert_display_content): + # pylint: disable=unused-argument """Test to validate iteration through a list using display content.""" logger.info("Starting test: test_iterate_list") @@ -21,4 +22,3 @@ def test_pulse_counter_start(assert_display_content): logger.info("Firmware started for the simulation.") time.sleep(0.1) - diff --git a/Test/Unit/Driver/test_PulseCounterDriver.cpp b/Test/Unit/Driver/test_PulseCounterDriver.cpp index 4c7546c4..3edede15 100644 --- a/Test/Unit/Driver/test_PulseCounterDriver.cpp +++ b/Test/Unit/Driver/test_PulseCounterDriver.cpp @@ -38,6 +38,9 @@ TEST_F(PulseCounterDriverTest, IncrementCounterIncreasesCorrectValue) { auto idOfCounter = static_cast(id); + EXPECT_TRUE(driver->onInitialize()); + EXPECT_TRUE(driver->onStart()); + incrementPulseCounter(idOfCounter); EXPECT_EQ(driver->getMeasurement(), 1u); @@ -49,6 +52,9 @@ TEST_F(PulseCounterDriverTest, ClearMeasurementResetsCounter) { auto idOfCounter = static_cast(id); + EXPECT_TRUE(driver->onInitialize()); + EXPECT_TRUE(driver->onStart()); + incrementPulseCounter(idOfCounter); EXPECT_EQ(driver->getMeasurement(), 1u); diff --git a/Test/Unit/README.md b/Test/Unit/README.md new file mode 100644 index 00000000..8ad842e3 --- /dev/null +++ b/Test/Unit/README.md @@ -0,0 +1 @@ +[Setup Manual](../../Documentation/Manuals/SetupFirmwareDevelopment.md) diff --git a/docker-compose.ci.yml b/docker-compose.ci.yml index aaa4e7a2..18bbed41 100644 --- a/docker-compose.ci.yml +++ b/docker-compose.ci.yml @@ -9,7 +9,7 @@ services: - ./Software:/workspace/Software - ./Simulation:/workspace/Simulation - ./Test:/workspace/Test - - ./DevOps/HwDev/BuildArtifacts:/workspace/build/BuildArtifacts + - ./DevOps/BuildArtifacts:/workspace/build/BuildArtifacts - ./CMakeLists.txt:/workspace/CMakeLists.txt - /tmp/.X11-unix:/tmp/.X11-unix # Mount X server socket working_dir: /workspace diff --git a/docker-compose.yml b/docker-compose.yml index 9d68da53..f5a688db 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,7 +3,7 @@ services: image: logger-dev:latest build: context: . - dockerfile: DevOps/HwDev/Docker/Dockerfile + dockerfile: DevOps/Dockerfile privileged: true environment: - DISPLAY=${DISPLAY} # Forward the host's display @@ -13,7 +13,7 @@ services: - ./Software:/workspace/Software - ./Simulation:/workspace/Simulation - ./Test:/workspace/Test - - ./DevOps/HwDev/BuildArtifacts:/workspace/build/BuildArtifacts + - ./DevOps/BuildArtifacts:/workspace/build/BuildArtifacts - ./CMakeLists.txt:/workspace/CMakeLists.txt - /tmp/.X11-unix:/tmp/.X11-unix # Mount X server socket working_dir: /workspace