feat: retain multi-channel voltage telemetry
This commit is contained in:
1 parent
ad1ff3f03e
commit
131bbafc67
17 files changed
+1603
-138
No files matched your search
+1
-1
@@ -41,7 +41,7 @@ does not reflect the GoogleTest count -- run the built binary directly
|
||||
| `test_mqtt_prefs_atomic_store` | `src/helpers/MQTTPrefsAtomicStore.h` | transactional MQTT writes and legacy `/node_prefs` handoff; exact short-write detection; begin/finish/rename failure cleanup; original-file preservation |
|
||||
| `test_prefs_save_routing` | `src/helpers/PrefsSaveRouting.h` | runtime common/observer setters write only their owning preference image; mixed-owner setters and migrations can deliberately write both |
|
||||
| `test_mqtt_payload_builder` | `src/helpers/MQTTPayloadBuilder.cpp` | status/packet/raw JSON contracts; optional fields; escaping; RX metrics and path; score handling; exact buffer bounds; maximum representative payloads |
|
||||
| `test_telemetry_history` | `src/helpers/TelemetryHistory.h` | 30-minute rings; seven-day temperature/voltage and dynamically sized GPS retention; exact 1 C temperature/status encoding; separate Base64 series payloads; 14-bit GPS differentials; resize preservation, heap budgets, and 1-based paging bounds |
|
||||
| `test_telemetry_history` | `src/helpers/TelemetryHistory.h`, `ExternalVoltageHistory.h` | 30-minute rings; seven-day temperature/battery and four-day multi-channel I2C voltage retention; exact 1 C temperature/status and 0.02 V packed voltage encodings; zero-only channel omission; Base64 pages and raw chunks; GPS differentials, resize preservation, heap budgets, and paging bounds |
|
||||
| `test_flood_filter_policy` | `src/helpers/FloodFilterPolicy.h` | unordered blacklist matching; ordered 1/2/3-byte pbyte rule prefixes; original incoming scope classes and canonical region-name identity; channel-authentication cache key comparison; priority ordering and terminal stop masks; bridge-bucket and regionless channel-target selector encoding; `require=region` and per-channel scope-gate truth tables; fast/slow timing; adding, replacing, and preserving packet scope |
|
||||
| `test_logical_message_cache` | `src/helpers/LogicalMessageCache.h` | bounded logical-message mapping; stable retry timestamps; exact older retries after newer messages; stale and same-timestamp mismatch rejection |
|
||||
| `test_cli_command_utils` | `src/helpers/CLICommandUtils.h`, `src/helpers/ContactListOrder.h`, `src/helpers/TerminalCommandTracker.h`, `src/helpers/TerminalDisplayFilter.h` | terminal verb/argument/path parsing; routed receive labels; quiet display defaults and independent emergency filtering; favorite-first contact ordering; single-command reply matching, round-trip timing, and rollover-safe expiration |
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <helpers/ExternalVoltageHistory.h>
|
||||
#include <helpers/TelemetryHistory.h>
|
||||
|
||||
namespace {
|
||||
@@ -431,6 +433,155 @@ TEST(TelemetryHistory, ValidatesRetentionAndPagingArguments) {
|
||||
EXPECT_NE(nullptr, strstr(reply, "get telemetry.gps"));
|
||||
}
|
||||
|
||||
TEST(ExternalVoltageHistory, QuantizesFullLppRangeAtTwentyMillivolts) {
|
||||
using mesh::ExternalVoltageHistory;
|
||||
|
||||
EXPECT_EQ(0, ExternalVoltageHistory::encodeVoltage(0.0f));
|
||||
EXPECT_EQ(0, ExternalVoltageHistory::encodeVoltage(-1.0f));
|
||||
EXPECT_EQ(0, ExternalVoltageHistory::encodeVoltage(
|
||||
std::numeric_limits<float>::quiet_NaN()));
|
||||
EXPECT_EQ(0, ExternalVoltageHistory::encodeVoltage(12.0f, false));
|
||||
EXPECT_EQ(1, ExternalVoltageHistory::encodeVoltage(0.02f));
|
||||
EXPECT_EQ(617, ExternalVoltageHistory::encodeVoltage(12.34f));
|
||||
EXPECT_EQ(32767, ExternalVoltageHistory::encodeVoltage(655.34f));
|
||||
EXPECT_EQ(32767, ExternalVoltageHistory::encodeVoltage(700.0f));
|
||||
EXPECT_EQ(12340U, ExternalVoltageHistory::decodeMillivolts(617));
|
||||
EXPECT_EQ(655340U,
|
||||
ExternalVoltageHistory::decodeMillivolts(32767));
|
||||
}
|
||||
|
||||
TEST(ExternalVoltageHistory, OmitsChannelsWhoseHistoryIsEntirelyZero) {
|
||||
using mesh::ExternalVoltageHistory;
|
||||
ExternalVoltageHistory history;
|
||||
const uint8_t channels[] = {2, 3, 3};
|
||||
ASSERT_TRUE(history.configure(channels, sizeof(channels)));
|
||||
EXPECT_EQ(2, history.channelCount());
|
||||
EXPECT_EQ(2U * ExternalVoltageHistory::BYTES_PER_CHANNEL,
|
||||
history.storageBytes());
|
||||
|
||||
ExternalVoltageHistory::Reading readings[] = {
|
||||
{2, 0.0f, true}, {3, 0.0f, true}};
|
||||
history.record(1800000000U, readings, 2);
|
||||
EXPECT_EQ(0, history.populatedChannelCount());
|
||||
|
||||
char reply[160];
|
||||
EXPECT_FALSE(history.formatPageReply("", reply, sizeof(reply)));
|
||||
EXPECT_STREQ("Err - no connected I2C voltage channels", reply);
|
||||
|
||||
readings[1].voltage = 24.0f;
|
||||
history.record(1800000000U + ExternalVoltageHistory::SAMPLE_INTERVAL_SECONDS,
|
||||
readings, 2);
|
||||
EXPECT_EQ(1, history.populatedChannelCount());
|
||||
EXPECT_EQ(3, history.populatedChannelAt(0));
|
||||
EXPECT_FALSE(history.channelHasValue(2));
|
||||
EXPECT_TRUE(history.channelHasValue(3));
|
||||
EXPECT_TRUE(history.formatPageReply("", reply, sizeof(reply)));
|
||||
EXPECT_NE(nullptr, strstr(reply, "channels=3"));
|
||||
|
||||
readings[1].voltage = 0.0f;
|
||||
for (uint32_t sample = 0;
|
||||
sample < ExternalVoltageHistory::RETENTION_SAMPLES; sample++) {
|
||||
history.record(
|
||||
1800000000U
|
||||
+ (sample + 2U)
|
||||
* ExternalVoltageHistory::SAMPLE_INTERVAL_SECONDS,
|
||||
readings, 2);
|
||||
}
|
||||
EXPECT_EQ(ExternalVoltageHistory::RETENTION_SAMPLES,
|
||||
history.sampleCount());
|
||||
EXPECT_EQ(0, history.populatedChannelCount());
|
||||
EXPECT_FALSE(history.channelHasValue(3));
|
||||
EXPECT_FALSE(history.formatPageReply("", reply, sizeof(reply)));
|
||||
EXPECT_STREQ("Err - no connected I2C voltage channels", reply);
|
||||
}
|
||||
|
||||
TEST(ExternalVoltageHistory, RetainsFourDaysAndFormatsMultiChannelPages) {
|
||||
using mesh::ExternalVoltageHistory;
|
||||
ExternalVoltageHistory history;
|
||||
const uint8_t channels[] = {2, 7};
|
||||
ASSERT_TRUE(history.configure(channels, sizeof(channels)));
|
||||
const uint32_t first_bucket = 100000U;
|
||||
|
||||
for (uint32_t sample = 0;
|
||||
sample <= ExternalVoltageHistory::RETENTION_SAMPLES; sample++) {
|
||||
ExternalVoltageHistory::Reading readings[] = {
|
||||
{2, (float)(sample + 1U) * 0.02f, true},
|
||||
{7, (float)(sample + 101U) * 0.02f, true}};
|
||||
history.record((first_bucket + sample)
|
||||
* ExternalVoltageHistory::SAMPLE_INTERVAL_SECONDS,
|
||||
readings, 2);
|
||||
}
|
||||
|
||||
EXPECT_EQ(ExternalVoltageHistory::RETENTION_SAMPLES,
|
||||
history.sampleCount());
|
||||
EXPECT_EQ(2, history.populatedChannelCount());
|
||||
char reply[160];
|
||||
ASSERT_TRUE(history.formatPageReply("2 4", reply, sizeof(reply)));
|
||||
const std::vector<uint8_t> payload = decodeReply(reply);
|
||||
ASSERT_EQ(ExternalVoltageHistory::PAGE_HEADER_SIZE
|
||||
+ ExternalVoltageHistory::PAGE_DATA_SIZE,
|
||||
payload.size());
|
||||
EXPECT_EQ(ExternalVoltageHistory::PAGE_PAYLOAD_TYPE_V1, payload[0]);
|
||||
EXPECT_EQ((first_bucket + 1U)
|
||||
* ExternalVoltageHistory::SAMPLE_INTERVAL_SECONDS,
|
||||
uint32LE(&payload[1]));
|
||||
EXPECT_EQ(30, payload[5]);
|
||||
EXPECT_EQ(48, payload[6]);
|
||||
EXPECT_EQ(2, payload[7]);
|
||||
size_t bit_offset = 0;
|
||||
EXPECT_EQ(2U, readBits(&payload[ExternalVoltageHistory::PAGE_HEADER_SIZE],
|
||||
bit_offset, ExternalVoltageHistory::SAMPLE_BITS));
|
||||
|
||||
ASSERT_TRUE(history.formatPageReply("7 1", reply, sizeof(reply)));
|
||||
const std::vector<uint8_t> newest = decodeReply(reply);
|
||||
bit_offset = 0;
|
||||
EXPECT_EQ(246U,
|
||||
readBits(&newest[ExternalVoltageHistory::PAGE_HEADER_SIZE],
|
||||
bit_offset, ExternalVoltageHistory::SAMPLE_BITS));
|
||||
}
|
||||
|
||||
TEST(ExternalVoltageHistory, FormatsThreeRawChunksPerFullChannel) {
|
||||
using mesh::ExternalVoltageHistory;
|
||||
ExternalVoltageHistory history;
|
||||
const uint8_t channels[] = {2, 7};
|
||||
ASSERT_TRUE(history.configure(channels, sizeof(channels)));
|
||||
const uint32_t first_bucket = 200000U;
|
||||
for (uint32_t sample = 0;
|
||||
sample < ExternalVoltageHistory::RETENTION_SAMPLES; sample++) {
|
||||
ExternalVoltageHistory::Reading readings[] = {
|
||||
{2, (float)(sample + 1U) * 0.02f, true},
|
||||
{7, 0.0f, true}};
|
||||
history.record((first_bucket + sample)
|
||||
* ExternalVoltageHistory::SAMPLE_INTERVAL_SECONDS,
|
||||
readings, 2);
|
||||
}
|
||||
|
||||
EXPECT_EQ(3, history.binaryChunkCount());
|
||||
const uint8_t source_id[ExternalVoltageHistory::BINARY_SOURCE_ID_SIZE] = {
|
||||
0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF};
|
||||
uint8_t payload[ExternalVoltageHistory::BINARY_PAYLOAD_SIZE];
|
||||
for (uint8_t chunk = 0; chunk < 3; chunk++) {
|
||||
ASSERT_EQ(sizeof(payload), history.formatBinarySnapshot(
|
||||
source_id, 2, chunk,
|
||||
payload, sizeof(payload)));
|
||||
EXPECT_EQ(0, memcmp(payload, "IVB1", 4));
|
||||
EXPECT_EQ(0, memcmp(&payload[4], source_id, sizeof(source_id)));
|
||||
EXPECT_EQ((first_bucket + (uint32_t)chunk * 64U)
|
||||
* ExternalVoltageHistory::SAMPLE_INTERVAL_SECONDS,
|
||||
uint32LE(&payload[12]));
|
||||
EXPECT_EQ(30, uint16LE(&payload[16]));
|
||||
EXPECT_EQ(2, payload[18]);
|
||||
EXPECT_EQ(64, payload[19]);
|
||||
size_t bit_offset = 0;
|
||||
EXPECT_EQ(1U + (uint32_t)chunk * 64U,
|
||||
readBits(&payload[ExternalVoltageHistory::BINARY_HEADER_SIZE],
|
||||
bit_offset, ExternalVoltageHistory::SAMPLE_BITS));
|
||||
}
|
||||
|
||||
EXPECT_EQ(0U, history.formatBinarySnapshot(
|
||||
source_id, 7, 0, payload, sizeof(payload)));
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
|
||||
Reference in new issue
Block a user