diff --git a/TESTS/bike-computer/sensor-device/main.cpp b/TESTS/bike-computer/sensor-device/main.cpp new file mode 100644 index 0000000..9792526 --- /dev/null +++ b/TESTS/bike-computer/sensor-device/main.cpp @@ -0,0 +1,69 @@ +// Copyright 2022 Haute école d'ingénierie et d'architecture de Fribourg +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/**************************************************************************** + * @file main.cpp + * @author Serge Ayer + * + * @brief Bike computer test suite: sensor device + * + * @date 2023-08-26 + * @version 0.1.0 + ***************************************************************************/ + +#include "greentea-client/test_env.h" +#include "hdc1000.hpp" +#include "mbed.h" +#include "sensor_device.hpp" +#include "unity/unity.h" +#include "utest/utest.h" + +using namespace utest::v1; + +// test_hdc1000 test handler function +static control_t test_sensor_device(const size_t call_count) { + // create the SensorDevice instance + bike_computer::SensorDevice sensorDevice; + + bool rc = sensorDevice.init(); + TEST_ASSERT_TRUE(rc); + + float temperature = sensorDevice.readTemperature(); + static constexpr float kTemperatureRange = 20.0f; + static constexpr float kMeanTemperature = 15.0f; + TEST_ASSERT_FLOAT_WITHIN(kTemperatureRange, kMeanTemperature, temperature); + + float humidity = sensorDevice.readHumidity(); + static constexpr float kHumidityRange = 40.0f; + static constexpr float kMeanHumidity = 50.0f; + TEST_ASSERT_FLOAT_WITHIN(kHumidityRange, kMeanHumidity, humidity); + + // execute the test only once and move to the next one, without waiting + return CaseNext; +} + +static utest::v1::status_t greentea_setup(const size_t number_of_cases) { + // Here, we specify the timeout (60s) and the host test (a built-in host test or the + // name of our Python file) + GREENTEA_SETUP(60, "default_auto"); + + return greentea_test_setup_handler(number_of_cases); +} + +// List of test cases in this file +static Case cases[] = {Case("test sensor device", test_sensor_device)}; + +static Specification specification(greentea_setup, cases); + +int main() { return !Harness::run(specification); } \ No newline at end of file diff --git a/TESTS/bike-computer/speedometer/main.cpp b/TESTS/bike-computer/speedometer/main.cpp new file mode 100644 index 0000000..26a2906 --- /dev/null +++ b/TESTS/bike-computer/speedometer/main.cpp @@ -0,0 +1,354 @@ +// Copyright 2022 Haute école d'ingénierie et d'architecture de Fribourg +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/**************************************************************************** + * @file main.cpp + * @author Serge Ayer + * + * @brief Bike computer test suite: speedometer device + * + * @date 2023-08-26 + * @version 0.1.0 + ***************************************************************************/ + +#include + +#include "common/constants.hpp" +#include "common/speedometer.hpp" +#include "greentea-client/test_env.h" +#include "mbed.h" +#include "static_scheduling/gear_device.hpp" +#include "unity/unity.h" +#include "utest/utest.h" + +using namespace utest::v1; + +// allow for 0.1 km/h difference +static constexpr float kAllowedSpeedDelta = 0.1f; +// allow for 1m difference +static constexpr float kAllowedDistanceDelta = 1.0f / 1000.0; + +// function called by test handler functions for verifying the current speed +void check_current_speed(const std::chrono::milliseconds& pedalRotationTime, + uint8_t traySize, + uint8_t gearSize, + float wheelCircumference, + float currentSpeed) { + // compute the number of pedal rotation per hour + uint32_t milliSecondsPerHour = 1000 * 3600; + float pedalRotationsPerHour = static_cast(milliSecondsPerHour) / + static_cast(pedalRotationTime.count()); + + // compute the expected speed in km / h + // first compute the distance in meter for each pedal turn + float trayGearRatio = static_cast(traySize) / static_cast(gearSize); + float distancePerPedalTurn = trayGearRatio * wheelCircumference; + float expectedSpeed = (distancePerPedalTurn / 1000.0f) * pedalRotationsPerHour; + + printf(" Expected speed is %f, current speed is %f\n", expectedSpeed, currentSpeed); + TEST_ASSERT_FLOAT_WITHIN(kAllowedSpeedDelta, expectedSpeed, currentSpeed); +} + +// compute the traveled distance for a time interval +float compute_distance(const std::chrono::milliseconds& pedalRotationTime, + uint8_t traySize, + uint8_t gearSize, + float wheelCircumference, + const std::chrono::milliseconds& travelTime) { + // compute the number of pedal rotation during travel time + // both times are expressed in ms + float pedalRotations = static_cast(travelTime.count()) / + static_cast(pedalRotationTime.count()); + + // compute the distance in meter for each pedal turn + float trayGearRatio = static_cast(traySize) / static_cast(gearSize); + float distancePerPedalTurn = trayGearRatio * wheelCircumference; + + // distancePerPedalTurn is expressed in m, divide per 1000 for a distance in km + return (distancePerPedalTurn * pedalRotations) / 1000.0; +} + +// function called by test handler functions for verifying the distance traveled +void check_distance(const std::chrono::milliseconds& pedalRotationTime, + uint8_t traySize, + uint8_t gearSize, + float wheelCircumference, + const std::chrono::milliseconds& travelTime, + float distance) { + // distancePerPedalTurn is expressed in m, divide per 1000 for a distance in km + float expectedDistance = compute_distance( + pedalRotationTime, traySize, gearSize, wheelCircumference, travelTime); + printf(" Expected distance is %f, current distance is %f\n", + expectedDistance, + distance); + TEST_ASSERT_FLOAT_WITHIN(kAllowedDistanceDelta, expectedDistance, distance); +} + +// test the speedometer by modifying the gear +static control_t test_gear_size(const size_t call_count) { + // create a timer + Timer timer; + // start the timer + timer.start(); + + // create a speedometer instance + bike_computer::Speedometer speedometer(timer); + + // get speedometer constant values (for this test) + const auto traySize = speedometer.getTraySize(); + const auto wheelCircumference = speedometer.getWheelCircumference(); + const auto pedalRotationTime = speedometer.getCurrentPedalRotationTime(); + + for (uint8_t gearSize = bike_computer::kMinGearSize; + gearSize <= bike_computer::kMaxGearSize; + gearSize++) { + // set the gear + printf("Testing gear size %d\n", gearSize); + speedometer.setGearSize(gearSize); + + // get the current speed + auto currentSpeed = speedometer.getCurrentSpeed(); + + // check the speed against the expected one + check_current_speed( + pedalRotationTime, traySize, gearSize, wheelCircumference, currentSpeed); + } + + // execute the test only once and move to the next one, without waiting + return CaseNext; +} + +// test the speedometer by modifying the pedal rotation speed +static control_t test_rotation_speed(const size_t call_count) { + // create a timer + Timer timer; + // start the timer + timer.start(); + + // create a speedometer instance + bike_computer::Speedometer speedometer(timer); + + // set the gear size + speedometer.setGearSize(bike_computer::kMaxGearSize); + + // get speedometer constant values + const auto traySize = speedometer.getTraySize(); + const auto wheelCircumference = speedometer.getWheelCircumference(); + const auto gearSize = speedometer.getGearSize(); + + // first test increasing rotation speed (decreasing rotation time) + auto pedalRotationTime = speedometer.getCurrentPedalRotationTime(); + while (pedalRotationTime > bike_computer::kMinPedalRotationTime) { + // decrease the pedal rotation time + pedalRotationTime -= bike_computer::kDeltaPedalRotationTime; + speedometer.setCurrentRotationTime(pedalRotationTime); + + // get the current speed + const auto currentSpeed = speedometer.getCurrentSpeed(); + + // check the speed against the expected one + check_current_speed( + pedalRotationTime, traySize, gearSize, wheelCircumference, currentSpeed); + } + + // second test decreasing rotation speed (increasing rotation time) + pedalRotationTime = speedometer.getCurrentPedalRotationTime(); + while (pedalRotationTime < bike_computer::kMaxPedalRotationTime) { + // increase the pedal rotation time + pedalRotationTime += bike_computer::kDeltaPedalRotationTime; + speedometer.setCurrentRotationTime(pedalRotationTime); + + // get the current speed + const auto currentSpeed = speedometer.getCurrentSpeed(); + + // check the speed against the expected one + check_current_speed( + pedalRotationTime, traySize, gearSize, wheelCircumference, currentSpeed); + } + + // execute the test only once and move to the next one, without waiting + return CaseNext; +} + +// test the speedometer by modifying the pedal rotation speed +static control_t test_distance(const size_t call_count) { + // create a timer + Timer timer; + + // create a speedometer instance + bike_computer::Speedometer speedometer(timer); + + // set the gear size + speedometer.setGearSize(bike_computer::kMaxGearSize); + + // get speedometer constant values + const auto traySize = speedometer.getTraySize(); + const auto wheelCircumference = speedometer.getWheelCircumference(); + auto gearSize = speedometer.getGearSize(); + auto pedalRotationTime = speedometer.getCurrentPedalRotationTime(); + + // test different travel times + const std::chrono::milliseconds travelTimes[] = {500ms, 1000ms, 5s, 10s}; + const uint8_t nbrOfTravelTimes = sizeof(travelTimes) / sizeof(travelTimes[0]); + + // start the timer (for simulating bike start) + timer.start(); + + // first check travel distance without changing gear and rotation speed + std::chrono::milliseconds totalTravelTime = std::chrono::milliseconds::zero(); + for (uint8_t index = 0; index < nbrOfTravelTimes; index++) { + // run for the travel time and get the distance + ThisThread::sleep_for(travelTimes[index]); + + // get the distance traveled + const auto distance = speedometer.getDistance(); + + // accumulate travel time + totalTravelTime += travelTimes[index]; + + // check the distance vs the expected one + check_distance(pedalRotationTime, + traySize, + gearSize, + wheelCircumference, + totalTravelTime, + distance); + } + + // now change gear at each time interval + auto expectedDistance = speedometer.getDistance(); + for (uint8_t index = 0; index < nbrOfTravelTimes; index++) { + // update the gear size + gearSize++; + speedometer.setGearSize(gearSize); + + // run for the travel time and get the distance + ThisThread::sleep_for(travelTimes[index]); + + // compute the expected distance for this time segment + float distance = compute_distance(pedalRotationTime, + traySize, + gearSize, + wheelCircumference, + travelTimes[index]); + expectedDistance += distance; + + // get the distance traveled + const auto traveledDistance = speedometer.getDistance(); + + printf(" Expected distance is %f, current distance is %f\n", + expectedDistance, + traveledDistance); + TEST_ASSERT_FLOAT_WITHIN( + kAllowedDistanceDelta, expectedDistance, traveledDistance); + } + // now change rotation speed at each time interval + expectedDistance = speedometer.getDistance(); + for (uint8_t index = 0; index < nbrOfTravelTimes; index++) { + // update the rotation speed + pedalRotationTime += bike_computer::kDeltaPedalRotationTime; + speedometer.setCurrentRotationTime(pedalRotationTime); + + // run for the travel time and get the distance + ThisThread::sleep_for(travelTimes[index]); + + // compute the expected distance for this time segment + float distance = compute_distance(pedalRotationTime, + traySize, + gearSize, + wheelCircumference, + travelTimes[index]); + expectedDistance += distance; + + // get the distance traveled + const auto traveledDistance = speedometer.getDistance(); + + printf(" Expected distance is %f, current distance is %f\n", + expectedDistance, + traveledDistance); + TEST_ASSERT_FLOAT_WITHIN( + kAllowedDistanceDelta, expectedDistance, traveledDistance); + } + + // execute the test only once and move to the next one, without waiting + return CaseNext; +} + +// test the speedometer by modifying the pedal rotation speed +static control_t test_reset(const size_t call_count) { + // create a timer instance + Timer timer; + + // create a speedometer instance + bike_computer::Speedometer speedometer(timer); + + // set the gear size + speedometer.setGearSize(bike_computer::kMinGearSize); + + // get speedometer constant values + const auto traySize = speedometer.getTraySize(); + const auto wheelCircumference = speedometer.getWheelCircumference(); + const auto gearSize = speedometer.getGearSize(); + const auto pedalRotationTime = speedometer.getCurrentPedalRotationTime(); + + // start the timer (for simulating bike start) + timer.start(); + + // travel for 1 second + const auto travelTime = 1000ms; + ThisThread::sleep_for(travelTime); + + // check the expected distaance traveled + const auto expectedDistance = compute_distance( + pedalRotationTime, traySize, gearSize, wheelCircumference, travelTime); + + // get the distance traveled + auto traveledDistance = speedometer.getDistance(); + + printf(" Expected distance is %f, current distance is %f\n", + expectedDistance, + traveledDistance); + TEST_ASSERT_FLOAT_WITHIN(kAllowedDistanceDelta, expectedDistance, traveledDistance); + + // reset the speedometer + speedometer.reset(); + + // traveled distance should now be zero + traveledDistance = speedometer.getDistance(); + + printf(" Expected distance is %f, current distance is %f\n", 0.0f, traveledDistance); + TEST_ASSERT_FLOAT_WITHIN(kAllowedDistanceDelta, 0.0f, traveledDistance); + + // execute the test only once and move to the next one, without waiting + return CaseNext; +} + +static utest::v1::status_t greentea_setup(const size_t number_of_cases) { + // Here, we specify the timeout (60s) and the host test (a built-in host test or the + // name of our Python file) + GREENTEA_SETUP(180, "default_auto"); + + return greentea_test_setup_handler(number_of_cases); +} + +// List of test cases in this file +static Case cases[] = { + Case("test speedometer gear size change", test_gear_size), + Case("test speedometer rotation speed change", test_rotation_speed), + Case("test speedometer distance", test_distance), + Case("test speedometer reset", test_reset)}; + +static Specification specification(greentea_setup, cases); + +int main() { return !Harness::run(specification); } \ No newline at end of file