Avionics
Core avionics package for CURE flight computers
Loading...
Searching...
No Matches
Telemetry.cpp
Go to the documentation of this file.
2#include "ArduinoHAL.h"
3#include <algorithm>
4#include <cstdint>
5
6// Helpers for checking if the packet has room for more data.
7std::size_t bytesNeededForSSD(const SendableSensorData* ssd) {
8 // Each SSD writes 1 label byte (name/label) plus 4 bytes per float value.
9 if (ssd->isSingle()) {
11 }
12 if (ssd->isMulti()) {
13 // label + N floats
14 return 1U + (static_cast<std::size_t>(ssd->multiSDHLength) * TelemetryFmt::kBytesInU32_bytes);
15 }
16 return 0U;
17}
18
19bool hasRoom(std::size_t nextIndex, std::size_t bytesToAdd) {
20 return nextIndex + bytesToAdd <= TelemetryFmt::kPacketCapacity_bytes;
21}
22
23bool isTimestampNewer(std::uint32_t lhs, std::uint32_t rhs) {
24 return static_cast<std::int32_t>(lhs - rhs) > 0;
25}
26
27bool isTimestampReachedOrPassed(std::uint32_t current, std::uint32_t target) {
28 return static_cast<std::int32_t>(current - target) >= 0;
29}
30
31void Telemetry::checkForRadioCommandSequence(std::uint32_t currentTime_ms) {
32 if (inCommandMode_) {
33 return;
34 }
35
36 while (rfdSerialConnection_.available() > 0) {
37 const char receivedChar = static_cast<char>(rfdSerialConnection_.read());
38
39 if (receivedChar == TelemetryFmt::kCommandEntryChar) {
40 ++commandEntryProgress_;
41 if (commandEntryProgress_ >= TelemetryFmt::kCommandEntrySequenceLength) {
42 enterCommandMode(currentTime_ms);
43 }
44 } else {
45 // Send a debug message to the stream
46 rfdSerialConnection_.print("Received char '");
47 rfdSerialConnection_.print(receivedChar);
48 rfdSerialConnection_.print("' which does not match command entry char '");
49 rfdSerialConnection_.print(TelemetryFmt::kCommandEntryChar);
50 rfdSerialConnection_.println("'. Resetting command entry progress.");
51 commandEntryProgress_ = 0;
52 }
53 }
54}
55
56void Telemetry::enterCommandMode(std::uint32_t currentTime_ms) {
57 inCommandMode_ = true;
58 commandModeEnteredTimestamp_ms_ = currentTime_ms;
59 commandModeLastInputTimestamp_ms_ = currentTime_ms;
60 commandEntryProgress_ = 0;
61 commandModeTimeoutLocked_ = false;
62 commandModeTimeoutLockDeadline_ms_ = 0;
63
64 if (commandLine_ != nullptr) {
65 commandLine_->switchUART(&rfdSerialConnection_);
66 commandLine_->print(kShellPrompt);
67 }
68}
69
70void Telemetry::exitCommandMode() {
71 inCommandMode_ = false;
72 commandModeTimeoutLocked_ = false;
73 commandModeTimeoutLockDeadline_ms_ = 0;
74
75 if (commandLine_ != nullptr) {
76 commandLine_->useDefaultUART();
77 }
78}
79
80void Telemetry::lockCommandModeTimeout(std::uint32_t lockDuration_ms) {
81 if (!inCommandMode_ || lockDuration_ms == 0) {
82 return;
83 }
84
85 const auto now_ms = static_cast<std::uint32_t>(millis());
86 commandModeTimeoutLocked_ = true;
87 commandModeTimeoutLockDeadline_ms_ = now_ms + lockDuration_ms;
88 commandModeLastInputTimestamp_ms_ = now_ms;
89}
90
92 commandModeTimeoutLocked_ = false;
93 commandModeTimeoutLockDeadline_ms_ = 0;
94
95 if (inCommandMode_) {
96 commandModeLastInputTimestamp_ms_ = static_cast<std::uint32_t>(millis());
97 }
98}
99
101 if (!inCommandMode_) {
102 return;
103 }
104
105 exitCommandMode();
106}
107
108bool Telemetry::shouldPauseTelemetryForCommandMode(std::uint32_t currentTime_ms) {
109 if (!inCommandMode_) {
110 return false;
111 }
112
113 if (commandLine_ != nullptr) {
114 const std::uint32_t lastInteractionTimestamp = commandLine_->getLastInteractionTimestamp();
115 if (isTimestampNewer(lastInteractionTimestamp, commandModeLastInputTimestamp_ms_)) {
116 commandModeLastInputTimestamp_ms_ = lastInteractionTimestamp;
117 }
118 }
119
120 // If currentTime_ms was sampled before command input was processed in this loop,
121 // avoid unsigned underflow in the inactivity subtraction.
122 if (isTimestampNewer(commandModeLastInputTimestamp_ms_, currentTime_ms)) {
123 return true;
124 }
125
126 if (commandModeTimeoutLocked_) {
127 if (!isTimestampReachedOrPassed(currentTime_ms, commandModeTimeoutLockDeadline_ms_)) {
128 return true;
129 }
130
131 commandModeTimeoutLocked_ = false;
132 commandModeTimeoutLockDeadline_ms_ = 0;
133 commandModeLastInputTimestamp_ms_ = currentTime_ms;
134 return true;
135 }
136
137 if ((currentTime_ms - commandModeLastInputTimestamp_ms_) >= TelemetryFmt::kCommandModeInactivityTimeout_ms) {
138 exitCommandMode();
139 return false;
140 }
141
142 return true;
143}
144
145void Telemetry::preparePacket(std::uint32_t timestamp_ms) {
146 // Write the packet header with sync bytes, start byte, and timestamp.
147 // Only clear what we own in the header (full-packet clearing happens in setPacketToZero()).
148
149 // Fill sync bytes with 0
150 std::fill_n(&this->packet_[0], TelemetryFmt::kSyncZeroCount_bytes, static_cast<std::uint8_t>(0));
151
152 // Set the start byte after the sync bytes
154
155 // Write the timestamp in big-endian format
156 TelemetryFmt::writeU32Be(&this->packet_[TelemetryFmt::kTimestampIndex], timestamp_ms);
157
158 // Packet counter (4 bytes, big-endian)
159 TelemetryFmt::writeU32Be(&this->packet_[TelemetryFmt::kPacketCounterIndex], packetCounter_);
160
161 nextEmptyPacketIndex_ = TelemetryFmt::kHeaderSize_bytes;
162}
163
164void Telemetry::addSingleSDHToPacket(SensorDataHandler* sdh) {
165 float floatData = sdh->getLastDataPointSaved().data;
166 uint32_t data = 0;
167 memcpy(&data, &floatData, sizeof(data)); // Move float data into an uint32_t for bytewise access
168 TelemetryFmt::writeU32Be(&this->packet_[nextEmptyPacketIndex_], data);
169 nextEmptyPacketIndex_ += TelemetryFmt::kBytesInU32_bytes;
170}
171
172void Telemetry::addSSDToPacket(SendableSensorData* ssd) {
173 if (ssd->isSingle()) {
174 this->packet_[nextEmptyPacketIndex_] = ssd->singleSDH->getName();
175 nextEmptyPacketIndex_ += 1;
176 this->addSingleSDHToPacket(ssd->singleSDH);
177 }
178 if (ssd->isMulti()) {
179 this->packet_[nextEmptyPacketIndex_] = ssd->multiSDHDataLabel;
180 nextEmptyPacketIndex_ += 1;
181 for (size_t i = 0; i < ssd->multiSDHLength; i++) {
182 // multiSDHLength comes directly from the array passed in by the client
183 // So, we can ignore this raw pointer indexing warning
184 this->addSingleSDHToPacket(ssd->multiSDH[i]); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
185 }
186 }
187}
188
189void Telemetry::setPacketToZero() {
190 for (size_t i = 0; i < TelemetryFmt::kPacketCapacity_bytes; i++) { // Completely clear packet
191 this->packet_[i] = 0;
192 }
193}
194
195void Telemetry::addEndMarker() {
196 // Add the following 4 bytes to the end of the packet: 0x00 0x00 0x00 (kEndByteValue).
197
198 std::fill_n(&this->packet_[nextEmptyPacketIndex_], TelemetryFmt::kSyncZeroCount_bytes, static_cast<std::uint8_t>(0));
199 this->packet_[nextEmptyPacketIndex_+TelemetryFmt::kSyncZeroCount_bytes] = TelemetryFmt::kEndByteValue;
200 nextEmptyPacketIndex_ += TelemetryFmt::kEndMarkerSize_bytes;
201}
202
203bool Telemetry::canFitStreamWithEndMarker(const SendableSensorData* ssd) const {
204 const std::size_t payloadSize_bytes = bytesNeededForSSD(ssd);
205 return hasRoom(nextEmptyPacketIndex_, payloadSize_bytes + TelemetryFmt::kEndMarkerSize_bytes);
206}
207
208void Telemetry::tryAppendStream(SendableSensorData* stream, std::uint32_t currentTime_ms, bool& payloadAdded) {
209 if (!stream->shouldBeSent(currentTime_ms)) {
210 return;
211 }
212
213 if (!canFitStreamWithEndMarker(stream)) {
214 return;
215 }
216
217 addSSDToPacket(stream);
218 stream->markWasSent(currentTime_ms);
219 payloadAdded = true;
220}
221
222bool Telemetry::finalizeAndSendPacket() {
223 if (nextEmptyPacketIndex_ <= TelemetryFmt::kHeaderSize_bytes) {
224 return false;
225 }
226
227 if (!hasRoom(nextEmptyPacketIndex_, TelemetryFmt::kEndMarkerSize_bytes)) {
228 return false;
229 }
230
231 addEndMarker();
232 for (std::size_t i = 0; i < nextEmptyPacketIndex_; i++) {
233 rfdSerialConnection_.write(packet_[i]);
234 }
235
236 packetCounter_++;
237 return true;
238}
239
240bool Telemetry::tick(uint32_t currentTime_ms) {
241 // Checks if we should put the telemetry into command mode
242 checkForRadioCommandSequence(currentTime_ms);
243
244 if (shouldPauseTelemetryForCommandMode(currentTime_ms)) {
245 return false;
246 }
247
248 setPacketToZero();
249 preparePacket(currentTime_ms);
250
251 bool payloadAdded = false;
252
253 for (std::size_t i = 0; i < streamCount_; i++) {
254 // i is safe because the stream count comes from the array passed in by the client.
255 tryAppendStream(streams_[i], currentTime_ms, payloadAdded); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
256 }
257
258 if (!payloadAdded) {
259 return false;
260 }
261
262 return finalizeAndSendPacket();
263}
bool isTimestampReachedOrPassed(std::uint32_t current, std::uint32_t target)
Definition Telemetry.cpp:27
bool isTimestampNewer(std::uint32_t lhs, std::uint32_t rhs)
Definition Telemetry.cpp:23
bool hasRoom(std::size_t nextIndex, std::size_t bytesToAdd)
Definition Telemetry.cpp:19
std::size_t bytesNeededForSSD(const SendableSensorData *ssd)
Definition Telemetry.cpp:7
Packs SensorDataHandler values into a fixed-size byte packet and streams them over a Stream (UART).
constexpr const char * kShellPrompt
float data
Definition DataPoint.h:14
Buffers sensor samples and forwards them to an IDataSaver at a controlled rate.
DataPoint getLastDataPointSaved() const
uint8_t getName() const
void forceExitCommandMode()
Immediately exit command mode if currently active.
void lockCommandModeTimeout(std::uint32_t lockDuration_ms)
Temporarily disable command-mode inactivity timeout.
Definition Telemetry.cpp:80
void unlockCommandModeTimeout()
Re-enable command-mode inactivity timeout immediately.
Definition Telemetry.cpp:91
bool tick(std::uint32_t currentTime_ms)
Call every loop to send due telemetry streams.
void writeU32Be(std::uint8_t *dst, std::uint32_t v)
Write a 32-bit value in big-endian order to dst[0..3].
Definition Telemetry.h:68
constexpr std::size_t kPacketCapacity_bytes
Definition Telemetry.h:32
constexpr std::size_t kStartByteIndex
Definition Telemetry.h:41
constexpr char kCommandEntryChar
Definition Telemetry.h:59
constexpr std::size_t kHeaderSize_bytes
Definition Telemetry.h:44
constexpr std::size_t kBytesInU32_bytes
Definition Telemetry.h:38
constexpr std::size_t kCommandEntrySequenceLength
Definition Telemetry.h:58
constexpr std::uint8_t kStartByteValue
Definition Telemetry.h:50
constexpr std::size_t kPacketCounterIndex
Definition Telemetry.h:43
constexpr std::size_t kEndMarkerSize_bytes
Definition Telemetry.h:47
constexpr std::uint32_t kCommandModeInactivityTimeout_ms
Definition Telemetry.h:57
constexpr std::size_t kSyncZeroCount_bytes
Definition Telemetry.h:35
constexpr std::uint8_t kEndByteValue
Definition Telemetry.h:53
constexpr std::size_t kTimestampIndex
Definition Telemetry.h:42
Declares one telemetry "stream" to include in packets.
Definition Telemetry.h:103
const std::size_t multiSDHLength
Definition Telemetry.h:115
bool isMulti() const
Convenience: true if configured as a multi SDH stream.
Definition Telemetry.h:176
bool isSingle() const
Convenience: true if configured as a single SDH stream.
Definition Telemetry.h:173
void markWasSent(std::uint32_t now_ms)
Update internal state after sending.
Definition Telemetry.h:168
std::uint8_t multiSDHDataLabel
Definition Telemetry.h:118
bool shouldBeSent(std::uint32_t now_ms) const
Return true if enough time has elapsed such that this stream wants to be sent again.
Definition Telemetry.h:161
SensorDataHandler *const * multiSDH
Definition Telemetry.h:110
SensorDataHandler * singleSDH
Definition Telemetry.h:107