diff --git a/cmake/boardcore.cmake b/cmake/boardcore.cmake index 22c1ee78d2b3fce712f1596061739c9ca0ddb45f..067c01b4ec674882dbf6d7e5eee58a528a112800 100644 --- a/cmake/boardcore.cmake +++ b/cmake/boardcore.cmake @@ -43,7 +43,7 @@ set(BOARDCORE_SRC ${BOARDCORE_PATH}/src/shared/algorithms/NAS/NAS.cpp ${BOARDCORE_PATH}/src/shared/algorithms/NAS/StateInitializer.cpp ${SBS_BASE}/src/shared/algorithms/Propagator/Propagator.cpp - + ${SBS_BASE}/src/shared/algorithms/Follower/Follower.cpp # Debug ${BOARDCORE_PATH}/src/shared/utils/Debug.cpp diff --git a/src/shared/algorithms/Follower/Follower.cpp b/src/shared/algorithms/Follower/Follower.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a6006fdeea4620a473c9d929c04f631c5b3291f6 --- /dev/null +++ b/src/shared/algorithms/Follower/Follower.cpp @@ -0,0 +1,229 @@ +/* Copyright (c) 2024 Skyward Experimental Rocketry + * Authors: Emilio Corigliano, Niccolò Betto, Federico Lolli + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "Follower.h" + +#include <drivers/timer/TimestampTimer.h> +#include <utils/AeroUtils/AeroUtils.h> + +#include <Eigen/Dense> +#include <cmath> +#include <iostream> + +using namespace miosix; + +namespace Boardcore +{ + +Follower::Follower(float updatePeriod) + : updatePeriod(updatePeriod), targetAngles({0, 0, 0}) +{ +} + +void Follower::setAntennaCoordinates(const Boardcore::GPSData& gpsData) +{ + Lock<FastMutex> lock(lastAntennaAttitudeMutex); + antennaCoordinates = {gpsData.latitude, gpsData.longitude, gpsData.height}; + Boardcore::Logger::getInstance().log( + static_cast<LogAntennasCoordinates>(gpsData)); + antennaCoordinatesSet = true; +} + +void Follower::setRocketNASOrigin(const Boardcore::GPSData& gpsData) +{ + Lock<FastMutex> lock(lastAntennaAttitudeMutex); + rocketNASOrigin = {gpsData.latitude, gpsData.longitude, gpsData.height}; + Boardcore::Logger::getInstance().log( + static_cast<LogRocketCoordinates>(gpsData)); + rocketCoordinatesSet = true; +} + +void Follower::setLastAntennaAttitude(const VN300Data& attitudeData) +{ + Lock<FastMutex> lock(lastAntennaAttitudeMutex); + lastAntennaAttitude = attitudeData; +} + +VN300Data Follower::getLastAntennaAttitude() +{ + Lock<FastMutex> lock(lastAntennaAttitudeMutex); + return lastAntennaAttitude; +} + +void Follower::setLastRocketNasState(const NASState nasState) +{ + Lock<FastMutex> lock(lastRocketNasStateMutex); + lastRocketNasState = nasState; + firstAntennaAttitudeSet = true; +} + +NASState Follower::getLastRocketNasState() +{ + Lock<FastMutex> lock(lastRocketNasStateMutex); + return lastRocketNasState; +} + +FollowerState Follower::getState() +{ + miosix::Lock<miosix::FastMutex> lock(stateMutex); + return state; +} + +void Follower::setState(const FollowerState& newState) +{ + miosix::Lock<miosix::FastMutex> lock(stateMutex); + state = newState; +} + +bool Follower::init() +{ + if (!antennaCoordinatesSet || !rocketCoordinatesSet) + { + LOG_ERR(logger, "Antenna or rocket coordinates not set"); + return false; + } + + // Antenna Coordinates + Eigen::Vector2f antennaCoord{getAntennaCoordinates().head<2>()}; + // Rocket coordinates + Eigen::Vector2f rocketCoord{getRocketNASOrigin().head<2>()}; + + initialAntennaRocketDistance = + Aeroutils::geodetic2NED(rocketCoord, antennaCoord); + + LOG_INFO(logger, "Initial antenna - rocket distance: [{}, {}] [m]\n", + initialAntennaRocketDistance[0], initialAntennaRocketDistance[1]); + + return true; +} + +float Follower::minimizeRotation(float angle) +{ + if (angle > 180) + { + angle -= 360; + } + else if (angle < -180) + { + angle += 360; + } + + return angle; +} + +void Follower::step() +{ + NASState lastRocketNasState = getLastRocketNasState(); + + // Getting the position of the rocket wrt the antennas in NED frame + NEDCoords rocketPosition = {lastRocketNasState.n, lastRocketNasState.e, + lastRocketNasState.d}; + + // Calculate the antenna target angles from the NED rocket coordinates + targetAngles = rocketPositionToAntennaAngles(rocketPosition); + + // If attitude data has never been set, do not actuate the steppers + if (!firstAntennaAttitudeSet) + { + LOG_ERR(logger, "Antenna attitude not set"); + return; + } + + VN300Data vn300 = getLastAntennaAttitude(); + + // Calculate the amount to move from the current position + AntennaAngles diffAngles{targetAngles.timestamp, + targetAngles.yaw - vn300.yaw, + targetAngles.pitch - vn300.pitch}; + + // Rotate in the shortest direction + diffAngles.yaw = 0.1 * minimizeRotation(diffAngles.yaw); + diffAngles.pitch = minimizeRotation(diffAngles.pitch); + + // Calculate angular velocity for moving the antennas toward position + float horizontalSpeed = + std::abs((diffAngles.yaw * 1000) / (360 * updatePeriod)); + float verticalSpeed = + std::abs((diffAngles.pitch * 1000) / (360 * updatePeriod)); + + // Update the state of the follower + FollowerState state; + state.timestamp = TimestampTimer::getTimestamp(); + state.yaw = diffAngles.yaw; + state.pitch = diffAngles.pitch; + state.horizontalSpeed = horizontalSpeed; + state.verticalSpeed = verticalSpeed; + setState(state); + +#ifndef NDEBUG + std::cout << "[FOLLOWER] STEPPER " + << "Angles: [" << state.yaw << ", " << state.pitch << "] " + << "Speed: [" << state.horizontalSpeed << ", " + << state.verticalSpeed << "] VN300 measure: [" << vn300.yaw + << ", " << vn300.pitch << "]\n"; +#endif +} + +Eigen::Vector3f Follower::getAntennaCoordinates() +{ + Lock<FastMutex> lock(antennaCoordinatesMutex); + return Eigen::Vector3f{antennaCoordinates}; +} + +Eigen::Vector3f Follower::getRocketNASOrigin() +{ + Lock<FastMutex> lock(rocketNASOriginMutex); + return Eigen::Vector3f{rocketNASOrigin}; +} + +AntennaAngles Follower::rocketPositionToAntennaAngles( + const NEDCoords& rocketNed) +{ + // Antenna Coordinates + Eigen::Vector2f antennaCoord = getAntennaCoordinates().head<2>(); + + // Rocket coordinates + Eigen::Vector2f rocketCoord = getRocketNASOrigin().head<2>(); + + initialAntennaRocketDistance = + Aeroutils::geodetic2NED(rocketCoord, antennaCoord); + + // NED coordinates of the rocket in the NED antenna frame + NEDCoords ned = {rocketNed.n + initialAntennaRocketDistance.x(), + rocketNed.e + initialAntennaRocketDistance.y(), + rocketNed.d}; + + AntennaAngles angles; + angles.timestamp = TimestampTimer::getTimestamp(); + + // Calculate the horizontal angle relative to the NED frame + // std::atan2 outputs angles in radians, convert to degrees + angles.yaw = std::atan2(ned.e, ned.n) / EIGEN_PI * 180; + + float distance = std::sqrt(ned.n * ned.n + ned.e * ned.e); + // Calculate the vertical angle relative to the NED frame + angles.pitch = std::atan2(-ned.d, distance) / EIGEN_PI * 180; + + return angles; +} + +} // namespace Boardcore diff --git a/src/shared/algorithms/Follower/Follower.h b/src/shared/algorithms/Follower/Follower.h new file mode 100644 index 0000000000000000000000000000000000000000..3fb6817a137fd06c992a7f7f97d1c69f2d96ac92 --- /dev/null +++ b/src/shared/algorithms/Follower/Follower.h @@ -0,0 +1,177 @@ +/* Copyright (c) 2024 Skyward Experimental Rocketry + * Authors: Emilio Corigliano, Niccolò Betto, Federico Lolli + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <algorithms/Algorithm.h> +#include <algorithms/NAS/NASState.h> +#include <diagnostic/PrintLogger.h> +#include <logger/Logger.h> +#include <sensors/SensorData.h> +#include <sensors/Vectornav/VN300/VN300Data.h> + +#include <atomic> +#include <utils/ModuleManager/ModuleManager.hpp> + +#include "FollowerData.h" + +namespace Boardcore +{ + +class Follower : public Algorithm +{ +public: + /** + * @brief Constructor of the follower class. + * + * @param updatePeriod The period of update of the follower algorithm [s]. + */ + explicit Follower(float updatePeriod); + + /** + * @brief Check that both the antenna and rocket coordinates have been set. + * @return `true` if initial rocket-antenna distance has been set, + * `false` otherwise. + */ + bool init() override; + + /** + * @brief Setter for the GPS coordinates of the antenna. + */ + void setAntennaCoordinates(const GPSData& gpsData); + + /** + * @brief Setter for the GPS coordinates of the rocket's NAS origin + * reference. + */ + void setRocketNASOrigin(const GPSData& gpsData); + + /** + * @brief Setter for the NAS state of the rocket. + */ + void setLastRocketNasState(const NASState nasState); + + /** + * @brief Setter for the attitude of the antenna. + * @param attitudeData The VN300 data containing the attitude of the antenna + * (yaw, pitch) + */ + void setLastAntennaAttitude(const VN300Data& attitudeData); + + /** + * @brief Synchronized getter for the State of the follower algorithm. + * @returns The state of the follower algorithm. + */ + FollowerState getState(); + + /** + * @brief Getter for the target antenna position computed by the algorithm. + * @returns The target antenna positions. + */ + AntennaAngles getTargetAngles() { return targetAngles; } + +private: + /** + * @brief Use the last rocket NAS state and initial rocket-antenna distance + * to compute the target antenna angles, retrievable with `getTargetAngles`. + */ + void step() override; + + /** + * @brief Synchronized getter that returns a copy of the last antenna + * attitude of the antenna. + */ + VN300Data getLastAntennaAttitude(); + + /** + * @brief Synchronized getter that returns a copy of the last NAS state + * of the rocket + */ + NASState getLastRocketNasState(); + + /** + * @brief Calculates the target angles from the given NED coordinates that + * the antenna should point to. + */ + AntennaAngles rocketPositionToAntennaAngles(const NEDCoords& ned); + + /** + * @brief Minimize rotation angle. + * + * @param angle Angle of movement [deg] + * @return The minimized rotation angle [deg] + */ + float minimizeRotation(float angle); + + /** + * @brief Synchronized setter for the state of the follower algorithm. + * @warning Should NOT be called if not in a test. + */ + void setState(const FollowerState& newState); + + /** + * @brief Get for the GPS coordinates of the antenna. + */ + Eigen::Vector3f getAntennaCoordinates(); + + /** + * @brief Get for the GPS coordinates of the rocket's NAS origin reference. + */ + Eigen::Vector3f getRocketNASOrigin(); + + // actuation update period [s] + float updatePeriod; + + // max number of retries for GPS data acquisition + const uint8_t maxInitRetries = 120; + + bool antennaCoordinatesSet = false; + bool rocketCoordinatesSet = false; + bool firstAntennaAttitudeSet = false; + + VN300Data lastAntennaAttitude; + miosix::FastMutex lastAntennaAttitudeMutex; + + NASState lastRocketNasState; + miosix::FastMutex lastRocketNasStateMutex; + + // GPS coordinates of the antenna [lat, lon, alt] [deg, deg, m] + Eigen::Vector3f antennaCoordinates; + miosix::FastMutex antennaCoordinatesMutex; + // GPS coordinates of the NAS origin taken from reference origin [lat, lon, + // alt] [deg, deg, m] + Eigen::Vector3f rocketNASOrigin; + miosix::FastMutex rocketNASOriginMutex; + // Initial distance between the antenna and the rocket while in ramp [lat, + // lon, alt] [deg, deg, m] + Eigen::Vector2f initialAntennaRocketDistance; + + // Target yaw and pitch of the system [deg, deg] + AntennaAngles targetAngles; + + FollowerState state; + miosix::FastMutex stateMutex; + + PrintLogger logger = Logging::getLogger("Follower"); +}; + +} // namespace Boardcore diff --git a/src/shared/algorithms/Follower/FollowerData.h b/src/shared/algorithms/Follower/FollowerData.h new file mode 100644 index 0000000000000000000000000000000000000000..a3ae02756d777afa84c67ba8b8e2048a4180c974 --- /dev/null +++ b/src/shared/algorithms/Follower/FollowerData.h @@ -0,0 +1,116 @@ +/* Copyright (c) 2024 Skyward Experimental Rocketry + * Author: Emilio Corigliano + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <sensors/SensorData.h> + +namespace Boardcore +{ + +struct NEDCoords +{ + float n = 0; + float e = 0; + float d = 0; +}; + +/** + * @brief A structure for storing angles relative to the NED frame. + */ +struct AntennaAngles +{ + uint64_t timestamp = 0; + + float yaw; //!< Angle between the X axis (N axis) and the target position + //!< on the XY plane (NE plane), positive anti-clockwise [deg] + float pitch; //!< Angle between the XY plane (NE plane) and the target + //!< position [deg] + uint32_t nrPropagations = + 0; //!< Nr of propagations by the propagator (0 if no propagation) + + AntennaAngles() : timestamp{0}, yaw(0), pitch(0), nrPropagations{0} {}; + AntennaAngles(uint64_t timestamp, float yaw, float pitch) + : timestamp{timestamp}, yaw(yaw), pitch(pitch), nrPropagations{0} {}; + AntennaAngles(uint64_t timestamp, float yaw, float pitch, + uint32_t nrPropagations) + : timestamp{timestamp}, yaw(yaw), + pitch(pitch), nrPropagations{nrPropagations} {}; + + static std::string header() + { + return "timestamp,yaw,pitch,nrPropagations\n"; + } + + void print(std::ostream& os) const + { + os << timestamp << "," << yaw << "," << pitch << "," << nrPropagations + << "\n"; + } +}; + +struct FollowerState +{ + uint64_t timestamp; + float yaw; // [deg] + float pitch; // [deg] + float horizontalSpeed; + float verticalSpeed; + + FollowerState() + : timestamp(0), yaw(0), pitch(0), horizontalSpeed(0), verticalSpeed(0) + { + } + + static std::string header() + { + return "timestamp,yaw,pitch,horizontalSpeed,verticalSpeed\n"; + } + + void print(std::ostream& os) const + { + os << timestamp << "," << yaw << "," << pitch << "," << horizontalSpeed + << "," << verticalSpeed << "\n"; + } +}; + +/** + * @brief A structure for logging the ARP system coordinates set in the + * Follower. + */ +struct LogAntennasCoordinates : public GPSData +{ + explicit LogAntennasCoordinates(const GPSData& data) : GPSData(data) {} + + LogAntennasCoordinates() {} +}; + +/** + * @brief A structure for logging the Rocket coordinates set in the Follower. + */ +struct LogRocketCoordinates : public GPSData +{ + explicit LogRocketCoordinates(const GPSData& data) : GPSData(data) {} + + LogRocketCoordinates() {} +}; +} // namespace Boardcore diff --git a/src/shared/logger/LogTypes.h b/src/shared/logger/LogTypes.h index 98884a8dc501f617aba545be8ae4763b7e7d444c..dea1b762f93657f0f322b381a86cb435181a2c78 100644 --- a/src/shared/logger/LogTypes.h +++ b/src/shared/logger/LogTypes.h @@ -25,6 +25,7 @@ #include <actuators/Servo/ServoData.h> #include <actuators/stepper/StepperData.h> #include <algorithms/ADA/ADAData.h> +#include <algorithms/Follower/FollowerData.h> #include <algorithms/NAS/NASState.h> #include <algorithms/ReferenceValues.h> #include <diagnostic/CpuMeter/CpuMeter.h> @@ -177,6 +178,9 @@ void registerTypes(Deserializer& ds) ds.registerType<VN300Data>(); ds.registerType<VoltageData>(); ds.registerType<Xbee::XbeeStatus>(); + ds.registerType<FollowerState>(); + ds.registerType<LogAntennasCoordinates>(); + ds.registerType<LogRocketCoordinates>(); } } // namespace LogTypes