diff --git a/src/Groundstation/Common/Ports/EthernetBase.cpp b/src/Groundstation/Common/Ports/EthernetBase.cpp index 34ce2d8e658efefb2c7b0a1a896126d67fed2f08..52b2e982ab9f8b42fab1a529408c447377785fb6 100644 --- a/src/Groundstation/Common/Ports/EthernetBase.cpp +++ b/src/Groundstation/Common/Ports/EthernetBase.cpp @@ -137,8 +137,6 @@ ssize_t EthernetBase::receive(uint8_t* pkt, size_t max_len) ssize_t size = 0; WizIp dst_ip; uint16_t dst_port; - TRACE("Haloo\n"); - if (!sniffOtherGs) size = wiz5500->recvfrom(0, pkt, max_len, dst_ip, dst_port); else @@ -148,7 +146,6 @@ ssize_t EthernetBase::receive(uint8_t* pkt, size_t max_len) RECEIVE_PORT_TIMEOUT_MS); if (size <= 0 && sniffOtherGs) { - TRACE("Second sniff\n"); size = wiz5500->recvfrom(1, pkt, max_len, dst_ip, dst_port, RECEIVE_PORT_TIMEOUT_MS); } diff --git a/src/Groundstation/Common/Ports/EthernetBase.h b/src/Groundstation/Common/Ports/EthernetBase.h index e2c1a830a628ed5a7e4d7965ac215b341ce03518..82adc3c1a059cb1f629c8f531f7bad927f766dba 100644 --- a/src/Groundstation/Common/Ports/EthernetBase.h +++ b/src/Groundstation/Common/Ports/EthernetBase.h @@ -35,7 +35,7 @@ namespace Groundstation { // Timeout for the port receive -static constexpr uint16_t RECEIVE_PORT_TIMEOUT_MS = 200; +static constexpr uint16_t RECEIVE_PORT_TIMEOUT_MS = 500; Boardcore::WizIp genNewRandomIp(); Boardcore::WizMac genNewRandomMac(); diff --git a/src/Groundstation/RotatingPlatform/BoardStatus.cpp b/src/Groundstation/RotatingPlatform/BoardStatus.cpp new file mode 100644 index 0000000000000000000000000000000000000000..db4551f4361207a9683b9024887611c5fef00e92 --- /dev/null +++ b/src/Groundstation/RotatingPlatform/BoardStatus.cpp @@ -0,0 +1,122 @@ +/* Copyright (c) 2024 Skyward Experimental Rocketry + * Author: Nicolò Caruso + * + * 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 "BoardStatus.h" + +using namespace Boardcore; +using namespace Groundstation; +using namespace RotatingPlatform; + +bool BoardStatus::isMainRadioPresent() { return main_radio_present; } +bool BoardStatus::isPayloadRadioPresent() { return payload_radio_present; } +bool BoardStatus::isEthernetPresent() { return ethernet_present; } + +bool BoardStatus::start() +{ + if (!ActiveObject::start()) + return false; + + return true; +} + +void BoardStatus::setMainRadioPresent(bool present) +{ + main_radio_present = present; +} + +void BoardStatus::setPayloadRadioPresent(bool present) +{ + payload_radio_present = present; +} + +void BoardStatus::setEthernetPresent(bool present) +{ + ethernet_present = present; +} + +void BoardStatus::run() +{ + while (!shouldStop()) + { + miosix::Thread::sleep(Groundstation::RADIO_STATUS_PERIOD); + + Actuators* actuators = getModule<Actuators>(); + + mavlink_arp_tm_t tm = {0}; + tm.timestamp = TimestampTimer::getTimestamp(); /*< [us] Timestamp*/ + tm.pitch = 6969; /*< [deg] Current Pitch*/ + tm.roll = 666; /*< [deg] Current Roll*/ + tm.target_pitch = 3333; /*< [deg] Target Pitch*/ + tm.stepperX_pos = actuators->getCurrentDegPosition( + StepperList::STEPPER_X); /*< [deg] StepperX target pos*/ + tm.stepperX_delta = actuators->getDeltaAngleDeg( + StepperList::STEPPER_X); /*< [deg] StepperX target delta deg*/ + tm.stepperX_speed = actuators->getSpeed( + StepperList::STEPPER_X); /*< [rps] StepperX Speed*/ + tm.stepperY_pos = -1; /*< [deg] StepperY target pos*/ + tm.stepperY_delta = -1; /*< [deg] StepperY target delta deg*/ + tm.stepperY_speed = -1; /*< [rps] StepperY Speed*/ + tm.gps_latitude = -1; /*< [deg] Latitude*/ + tm.gps_longitude = -1; /*< [deg] Longitude*/ + tm.gps_height = -1; /*< [m] Altitude*/ + tm.log_number = + Logger::getInstance().getCurrentLogNumber(); /*< Log number*/ + + tm.battery_voltage = -420.0; + + if (main_radio_present) + { + tm.main_radio_present = 1; + + auto stats = getModule<LyraGS::RadioMain>()->getStats(); + tm.main_packet_tx_error_count = stats.send_errors; + tm.main_tx_bitrate = main_tx_bitrate.update(stats.bits_tx_count); + tm.main_packet_rx_success_count = stats.packet_rx_success_count; + tm.main_packet_rx_drop_count = stats.packet_rx_drop_count; + tm.main_rx_bitrate = main_rx_bitrate.update(stats.bits_rx_count); + tm.main_rx_rssi = stats.rx_rssi; + + last_main_stats = stats; + + Logger::getInstance().log(LyraGS::MainRadioLog{ + tm.timestamp, tm.main_packet_tx_error_count, tm.main_tx_bitrate, + tm.main_packet_rx_success_count, tm.main_packet_rx_drop_count, + tm.main_rx_bitrate, tm.main_rx_rssi}); + } + + if (ethernet_present) + { + auto stats = getModule<LyraGS::EthernetGS>()->getState(); + + tm.ethernet_present = 1; + tm.ethernet_status = (stats.link_up ? 1 : 0) | + (stats.full_duplex ? 2 : 0) | + (stats.based_100mbps ? 4 : 0); + } + + mavlink_message_t msg; + mavlink_msg_arp_tm_encode(Groundstation::ARP_SYSTEM_ID, + Groundstation::ARP_COMPONENT_ID, &msg, &tm); + + getModule<HubBase>()->dispatchIncomingMsg(msg); + } +} diff --git a/src/Groundstation/RotatingPlatform/BoardStatus.h b/src/Groundstation/RotatingPlatform/BoardStatus.h new file mode 100644 index 0000000000000000000000000000000000000000..e6e0d2aa2b42b129a6afae88909c7650c9271f5c --- /dev/null +++ b/src/Groundstation/RotatingPlatform/BoardStatus.h @@ -0,0 +1,139 @@ +/* Copyright (c) 2024 Skyward Experimental Rocketry + * Author: Nicolò Caruso + * + * 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 <ActiveObject.h> +#include <Groundstation/Common/Config/GeneralConfig.h> +#include <Groundstation/Common/Config/RadioConfig.h> +#include <Groundstation/Common/HubBase.h> +#include <Groundstation/Common/Radio/RadioBase.h> +#include <Groundstation/LyraGS/Ports/Ethernet.h> +#include <Groundstation/LyraGS/Radio/Radio.h> +#include <Groundstation/LyraGS/Radio/RadioData.h> +#include <Groundstation/RotatingPlatform/Actuators/Actuators.h> +#include <common/MavlinkLyra.h> +#include <drivers/timer/TimestampTimer.h> +#include <utils/DependencyManager/DependencyManager.h> +#include <utils/collections/CircularBuffer.h> + +namespace LyraGS +{ +class RadioMain; +class RadioPayload; +class EthernetGS; +} // namespace LyraGS + +namespace RotatingPlatform +{ + +/** + * @brief Utility to calculate the bitrate + */ +template <unsigned int WINDOW_SIZE, unsigned int PERIOD> +class BitrateCalculator +{ +public: + BitrateCalculator() {} + + /** + * @brief Update the calculator, should be called every PERIOD ms + */ + uint16_t update(uint32_t sample) + { + if (window.isFull()) + { + uint32_t last = window.pop(); + window.put(sample); + + uint16_t delta = sample - last; + + // window size is in ms, we want the result in s + return delta * 1000 / WINDOW_SIZE; + } + else + { + window.put(sample); + return 0; + } + } + +private: + Boardcore::CircularBuffer<uint32_t, WINDOW_SIZE / PERIOD> window; +}; + +/** + * @brief Class responsible for keeping track of radio status and metrics. + */ +class BoardStatus + : public Boardcore::InjectableWithDeps< + RotatingPlatform::Actuators, Groundstation::HubBase, + LyraGS::RadioMain, LyraGS::RadioPayload, LyraGS::EthernetGS>, + private Boardcore::ActiveObject +{ +public: + bool start(); + + /** + * @brief Check wether the main radio was found during boot. + */ + bool isMainRadioPresent(); + + /** + * @brief Check wether the payload radio was found during boot. + */ + bool isPayloadRadioPresent(); + + /** + * @brief Check wether the ethernet was found d\uring boot. + */ + bool isEthernetPresent(); + + void setMainRadioPresent(bool present); + void setPayloadRadioPresent(bool present); + void setEthernetPresent(bool present); + +private: + void run() override; + + Groundstation::RadioStats last_main_stats = {0}; + Groundstation::RadioStats last_payload_stats = {0}; + + BitrateCalculator<Groundstation::RADIO_BITRATE_WINDOW_SIZE, + Groundstation::RADIO_STATUS_PERIOD> + main_tx_bitrate; + BitrateCalculator<Groundstation::RADIO_BITRATE_WINDOW_SIZE, + Groundstation::RADIO_STATUS_PERIOD> + main_rx_bitrate; + BitrateCalculator<Groundstation::RADIO_BITRATE_WINDOW_SIZE, + Groundstation::RADIO_STATUS_PERIOD> + payload_tx_bitrate; + BitrateCalculator<Groundstation::RADIO_BITRATE_WINDOW_SIZE, + Groundstation::RADIO_STATUS_PERIOD> + payload_rx_bitrate; + + bool main_radio_present = false; + bool payload_radio_present = false; + bool ethernet_present = false; +}; + +} // namespace RotatingPlatform diff --git a/src/Groundstation/RotatingPlatform/Radio/Radio.cpp b/src/Groundstation/RotatingPlatform/Radio/Radio.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b3c06662a6048bc2a68850a69522bd1a0843b3ed --- /dev/null +++ b/src/Groundstation/RotatingPlatform/Radio/Radio.cpp @@ -0,0 +1,170 @@ +/* Copyright (c) 2023 Skyward Experimental Rocketry + * Author: Davide Mor + * + * 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 "Radio.h" + +using namespace RotatingPlatform; +using namespace Boardcore; +using namespace miosix; + +SX1278Fsk* radioMainGlobal{nullptr}; +SX1278Fsk* radioPayloadGlobal{nullptr}; + +void __attribute__((used)) MIOSIX_RADIO1_DIO0_IRQ() +{ + SX1278Fsk* instance = radioMainGlobal; + if (instance) + instance->handleDioIRQ(); +} + +void __attribute__((used)) MIOSIX_RADIO1_DIO1_IRQ() +{ + SX1278Fsk* instance = radioMainGlobal; + if (instance) + instance->handleDioIRQ(); +} + +void __attribute__((used)) MIOSIX_RADIO1_DIO3_IRQ() +{ + SX1278Fsk* instance = radioMainGlobal; + if (instance) + instance->handleDioIRQ(); +} + +void __attribute__((used)) MIOSIX_RADIO2_DIO0_IRQ() +{ + if (radioPayloadGlobal) + radioPayloadGlobal->handleDioIRQ(); +} + +void __attribute__((used)) MIOSIX_RADIO2_DIO1_IRQ() +{ + if (radioPayloadGlobal) + radioPayloadGlobal->handleDioIRQ(); +} + +void __attribute__((used)) MIOSIX_RADIO2_DIO3_IRQ() +{ + if (radioPayloadGlobal) + radioPayloadGlobal->handleDioIRQ(); +} + +bool RadioMain::start() +{ + std::unique_ptr<SX1278::ISX1278Frontend> frontend; + + if (hasBackup) + frontend = std::make_unique<EbyteFrontend>(radio1::txen::getPin(), + radio1::rxen::getPin()); + else + frontend = std::make_unique<Skyward433Frontend>(); + + std::unique_ptr<Boardcore::SX1278Fsk> sx1278 = + std::make_unique<Boardcore::SX1278Fsk>( + getModule<LyraGS::Buses>()->radio1_bus, radio1::cs::getPin(), + radio1::dio0::getPin(), radio1::dio1::getPin(), + radio1::dio3::getPin(), SPI::ClockDivider::DIV_64, + std::move(frontend)); + + // Set the global radio for IRQ + radioMainGlobal = sx1278.get(); + + // First check if the device is even connected + bool present = sx1278->checkVersion(); + + getModule<RotatingPlatform::BoardStatus>()->setMainRadioPresent(present); + + if (present) + { + // Configure the radio + if (sx1278->configure(Common::MAIN_RADIO_CONFIG) != + SX1278Fsk::Error::NONE) + return false; + + // Initialize if only if present + if (!RadioBase::start(std::move(sx1278))) + + return false; + } + + return true; +} + +bool RadioPayload::start() +{ + std::unique_ptr<SX1278::ISX1278Frontend> frontend; + std::unique_ptr<Boardcore::SX1278Fsk> sx1278; + if (hasBackup) + frontend = std::make_unique<EbyteFrontend>(radio2::txen::getPin(), + radio2::rxen::getPin()); + else + frontend = std::make_unique<Skyward433Frontend>(); + + sx1278 = std::make_unique<Boardcore::SX1278Fsk>( + getModule<LyraGS::Buses>()->radio2_bus, radio2::cs::getPin(), + radio2::dio0::getPin(), radio2::dio1::getPin(), radio2::dio3::getPin(), + SPI::ClockDivider::DIV_64, std::move(frontend)); + + // Set the global radio for IRQ + radioPayloadGlobal = sx1278.get(); + + // First check if the device is even connected + bool present = sx1278->checkVersion(); + + getModule<BoardStatus>()->setPayloadRadioPresent(present); + + if (present) + { + // Configure the radio + if (sx1278->configure(Common::PAYLOAD_RADIO_CONFIG) != + SX1278Fsk::Error::NONE) + { + return false; + } + + // Initialize if only if present + if (!RadioBase::start(std::move(sx1278))) + return false; + } + + return true; +} + +bool RadioMain::sendMsg(const mavlink_message_t& msg) +{ + if (txEnable) + return RadioBase::sendMsg(msg); + return false; +}; + +/** + * @brief Send a mavlink message through this radio if it has been enabled + * by dipSwitch + * + * @returns false when the queue is full. + */ +bool RadioPayload::sendMsg(const mavlink_message_t& msg) +{ + if (txEnable) + return Groundstation::RadioBase::sendMsg(msg); + return false; +}; diff --git a/src/Groundstation/RotatingPlatform/Radio/Radio.h b/src/Groundstation/RotatingPlatform/Radio/Radio.h new file mode 100644 index 0000000000000000000000000000000000000000..b2a732dc61e3320dc5c8f7fe8699783048f594ab --- /dev/null +++ b/src/Groundstation/RotatingPlatform/Radio/Radio.h @@ -0,0 +1,92 @@ +/* Copyright (c) 2023 Skyward Experimental Rocketry + * Author: Davide Mor + * + * 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 <Groundstation/Common/HubBase.h> +#include <Groundstation/Common/Ports/Serial.h> +#include <Groundstation/Common/Radio/RadioBase.h> +#include <Groundstation/LyraGS/Buses.h> +#include <Groundstation/RotatingPlatform/BoardStatus.h> +#include <interfaces-impl/hwmapping.h> +#include <radio/SX1278/SX1278Frontends.h> +#include <utils/DependencyManager/DependencyManager.h> + +namespace RotatingPlatform +{ + +class BoardStatus; + +class RadioMain : public Boardcore::InjectableWithDeps< + Boardcore::InjectableBase<Groundstation::RadioBase>, + LyraGS::Buses, BoardStatus> +{ +public: + [[nodiscard]] bool start(); + + explicit RadioMain(bool hasBackup) : hasBackup{hasBackup} {}; + + RadioMain() : hasBackup{false} {}; + + RadioMain(bool hasBackup, bool txEnable) + : hasBackup{hasBackup}, txEnable{txEnable} {}; + + /** + * @brief Send a mavlink message through this radio if it has been enabled + * by dipSwitch for transmission + * + * @returns false when the queue is full or if tx is not enabled. + */ + bool sendMsg(const mavlink_message_t& msg); + +private: + bool hasBackup = false; + bool txEnable = true; +}; +class RadioPayload : public Boardcore::InjectableWithDeps< + Boardcore::InjectableBase<Groundstation::RadioBase>, + LyraGS::Buses, BoardStatus> +{ +public: + [[nodiscard]] bool start(); + + explicit RadioPayload(bool hasBackup) : hasBackup{hasBackup} {}; + + RadioPayload() : hasBackup{false} {}; + + RadioPayload(bool hasBackup, bool txEnable) + : hasBackup{hasBackup}, txEnable{txEnable} {}; + + /** + * @brief Send a mavlink message through this radio if it has been enabled + * by dipSwitch for transmission + * + * @returns false when the queue is full or if tx is not enabled. + */ + bool sendMsg(const mavlink_message_t& msg); + +private: + bool hasBackup = false; + bool txEnable = true; +}; + +} // namespace RotatingPlatform diff --git a/src/Groundstation/RotatingPlatform/Radio/RadioData.h b/src/Groundstation/RotatingPlatform/Radio/RadioData.h new file mode 100644 index 0000000000000000000000000000000000000000..fa497e080f709132b29525a259e7ceb37bca1b6a --- /dev/null +++ b/src/Groundstation/RotatingPlatform/Radio/RadioData.h @@ -0,0 +1,62 @@ +/* Copyright (c) 2024 Skyward Experimental Rocketry + * Author: Nicolò Caruso + * + * 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 <stdint.h> + +#include <iostream> +#include <string> + +/** + * @brief Logging struct for the main radio informations + * + */ + +namespace RotatingPlatform +{ +struct MainRadioLog +{ + uint64_t timestamp = 0; + uint16_t main_packet_tx_error_count = 0; + uint32_t main_tx_bitrate = 0; + uint16_t main_packet_rx_success_count = 0; + uint16_t main_packet_rx_drop_count = 0; + uint32_t main_rx_bitrate = 0; + float main_rx_rssi = 0; + + static std::string header() + { + return "timestamp,main_packet_tx_error_count,main_tx_bitrate,main_" + "packet_rx_success_count,main_packet_rx_drop_count,main_rx_" + "bitrate,main_rx_rssi\n"; + } + + void print(std::ostream& os) const + { + os << timestamp << "," << main_packet_tx_error_count << "," + << main_tx_bitrate << "," << main_tx_bitrate << "," + << main_packet_rx_success_count << "," << main_packet_rx_drop_count + << "," << main_rx_bitrate << "," << main_rx_rssi << "\n"; + } +}; +} // namespace RotatingPlatform