mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-09-17 07:34:20 +00:00
589 lines
24 KiB
C++
589 lines
24 KiB
C++
#include <gtest/gtest.h>
|
|
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <limits>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <helpers/ExternalVoltageHistory.h>
|
|
#include <helpers/TelemetryHistory.h>
|
|
|
|
namespace {
|
|
|
|
int base64Value(char value) {
|
|
if (value >= 'A' && value <= 'Z') return value - 'A';
|
|
if (value >= 'a' && value <= 'z') return value - 'a' + 26;
|
|
if (value >= '0' && value <= '9') return value - '0' + 52;
|
|
if (value == '+') return 62;
|
|
if (value == '/') return 63;
|
|
return -1;
|
|
}
|
|
|
|
std::vector<uint8_t> decodeReply(const char* reply) {
|
|
EXPECT_EQ('>', reply[0]);
|
|
EXPECT_EQ(' ', reply[1]);
|
|
const char* encoded = reply + 2;
|
|
std::vector<uint8_t> decoded;
|
|
uint32_t accumulator = 0;
|
|
unsigned bits = 0;
|
|
for (const char* cursor = encoded; *cursor != 0 && *cursor != '='; cursor++) {
|
|
const int value = base64Value(*cursor);
|
|
EXPECT_GE(value, 0);
|
|
if (value < 0) break;
|
|
accumulator = (accumulator << 6) | (uint32_t)value;
|
|
bits += 6;
|
|
if (bits >= 8) {
|
|
bits -= 8;
|
|
decoded.push_back((uint8_t)(accumulator >> bits));
|
|
accumulator &= bits == 0 ? 0U : ((1U << bits) - 1U);
|
|
}
|
|
}
|
|
return decoded;
|
|
}
|
|
|
|
uint32_t uint32LE(const uint8_t* source) {
|
|
return (uint32_t)source[0]
|
|
| ((uint32_t)source[1] << 8)
|
|
| ((uint32_t)source[2] << 16)
|
|
| ((uint32_t)source[3] << 24);
|
|
}
|
|
|
|
uint16_t uint16LE(const uint8_t* source) {
|
|
return (uint16_t)source[0] | ((uint16_t)source[1] << 8);
|
|
}
|
|
|
|
int32_t int32LE(const uint8_t* source) {
|
|
return (int32_t)uint32LE(source);
|
|
}
|
|
|
|
uint32_t readBits(const uint8_t* source, size_t& bit_offset, uint8_t width) {
|
|
uint32_t value = 0;
|
|
for (uint8_t i = 0; i < width; i++) {
|
|
value = (value << 1)
|
|
| ((source[bit_offset / 8U] >> (7U - bit_offset % 8U)) & 1U);
|
|
bit_offset++;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
int signed14(uint32_t value) {
|
|
return (value & 0x2000U) != 0 ? (int)value - 0x4000 : (int)value;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(TelemetryHistory, EncodesTemperatureSentinelsAndEndpoints) {
|
|
using mesh::TelemetryHistory;
|
|
uint8_t status = 0xFF;
|
|
|
|
EXPECT_EQ(0, TelemetryHistory::encodeTemperature(0, false, status));
|
|
EXPECT_EQ(TelemetryHistory::TEMPERATURE_NONE, status);
|
|
EXPECT_EQ(0, TelemetryHistory::encodeTemperature(-51, true, status));
|
|
EXPECT_EQ(TelemetryHistory::TEMPERATURE_LOW, status);
|
|
EXPECT_EQ(0, TelemetryHistory::encodeTemperature(-50, true, status));
|
|
EXPECT_EQ(TelemetryHistory::TEMPERATURE_VALUE, status);
|
|
EXPECT_EQ(50, TelemetryHistory::encodeTemperature(0, true, status));
|
|
EXPECT_EQ(TelemetryHistory::TEMPERATURE_VALUE, status);
|
|
EXPECT_EQ(127, TelemetryHistory::encodeTemperature(77, true, status));
|
|
EXPECT_EQ(TelemetryHistory::TEMPERATURE_VALUE, status);
|
|
EXPECT_EQ(127, TelemetryHistory::encodeTemperature(78, true, status));
|
|
EXPECT_EQ(TelemetryHistory::TEMPERATURE_HIGH, status);
|
|
}
|
|
|
|
TEST(TelemetryHistory, EncodesVoltageSentinelsAndHundredths) {
|
|
using mesh::TelemetryHistory;
|
|
|
|
EXPECT_EQ(0, TelemetryHistory::encodeVoltage(0));
|
|
EXPECT_EQ(1, TelemetryHistory::encodeVoltage(1879));
|
|
EXPECT_EQ(2, TelemetryHistory::encodeVoltage(1880));
|
|
EXPECT_EQ(3, TelemetryHistory::encodeVoltage(1890));
|
|
EXPECT_EQ(254, TelemetryHistory::encodeVoltage(4400));
|
|
EXPECT_EQ(255, TelemetryHistory::encodeVoltage(4401));
|
|
}
|
|
|
|
TEST(TelemetryHistory, FormatsRollingDayAndExplicitMissingBuckets) {
|
|
using mesh::TelemetryHistory;
|
|
TelemetryHistory history;
|
|
const uint32_t first_bucket = 1000000U;
|
|
const uint32_t first_epoch = first_bucket * TelemetryHistory::SAMPLE_INTERVAL_SECONDS;
|
|
|
|
history.record(first_epoch, -51, true, 1880, 0, 0, false);
|
|
history.record(first_epoch + 2U * TelemetryHistory::SAMPLE_INTERVAL_SECONDS,
|
|
78, true, 4400, 0, 0, false);
|
|
|
|
char reply[160];
|
|
ASSERT_TRUE(history.formatPageReply(TelemetryHistory::SERIES_TEMPERATURE,
|
|
"", reply, sizeof(reply)));
|
|
EXPECT_EQ(86U, strlen(reply));
|
|
const std::vector<uint8_t> temperature_payload = decodeReply(reply);
|
|
ASSERT_EQ(61U, temperature_payload.size());
|
|
EXPECT_EQ(TelemetryHistory::TEMPERATURE_PAYLOAD_TYPE_V1,
|
|
temperature_payload[0]);
|
|
EXPECT_EQ((first_bucket + 2U - 47U) * TelemetryHistory::SAMPLE_INTERVAL_SECONDS,
|
|
uint32LE(&temperature_payload[1]));
|
|
EXPECT_EQ(30, temperature_payload[5]);
|
|
EXPECT_EQ(48, temperature_payload[6]);
|
|
|
|
size_t status_offset = 0;
|
|
size_t bit_offset = 0;
|
|
for (int slot = 0; slot < 48; slot++) {
|
|
const uint8_t status = (uint8_t)readBits(&temperature_payload[7],
|
|
status_offset, 2);
|
|
const uint8_t temperature = (uint8_t)readBits(&temperature_payload[19],
|
|
bit_offset, 7);
|
|
if (slot == 45) {
|
|
EXPECT_EQ(TelemetryHistory::TEMPERATURE_LOW, status);
|
|
EXPECT_EQ(0, temperature);
|
|
} else if (slot == 47) {
|
|
EXPECT_EQ(TelemetryHistory::TEMPERATURE_HIGH, status);
|
|
EXPECT_EQ(127, temperature);
|
|
} else {
|
|
EXPECT_EQ(TelemetryHistory::TEMPERATURE_NONE, status);
|
|
EXPECT_EQ(0, temperature);
|
|
}
|
|
}
|
|
|
|
ASSERT_TRUE(history.formatPageReply(TelemetryHistory::SERIES_VOLTAGE,
|
|
"1", reply, sizeof(reply)));
|
|
EXPECT_EQ(78U, strlen(reply));
|
|
const std::vector<uint8_t> voltage_payload = decodeReply(reply);
|
|
ASSERT_EQ(55U, voltage_payload.size());
|
|
EXPECT_EQ(TelemetryHistory::VOLTAGE_PAYLOAD_TYPE_V1, voltage_payload[0]);
|
|
EXPECT_EQ((first_bucket + 2U - 47U) * TelemetryHistory::SAMPLE_INTERVAL_SECONDS,
|
|
uint32LE(&voltage_payload[1]));
|
|
for (int slot = 0; slot < 48; slot++) {
|
|
if (slot == 45) {
|
|
EXPECT_EQ(2, voltage_payload[7 + slot]);
|
|
} else if (slot == 47) {
|
|
EXPECT_EQ(254, voltage_payload[7 + slot]);
|
|
} else {
|
|
EXPECT_EQ(0, voltage_payload[7 + slot]);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(TelemetryHistory, RetainsExactlySevenTemperatureVoltageDays) {
|
|
using mesh::TelemetryHistory;
|
|
TelemetryHistory history;
|
|
const uint32_t first_bucket = 1000U;
|
|
for (uint32_t sample = 0; sample <= TelemetryHistory::TV_RETENTION_SAMPLES; sample++) {
|
|
history.record((first_bucket + sample) * TelemetryHistory::SAMPLE_INTERVAL_SECONDS,
|
|
0, true, (uint16_t)(1880U + (sample % 253U) * 10U),
|
|
0, 0, false);
|
|
}
|
|
|
|
char reply[160];
|
|
ASSERT_TRUE(history.formatPageReply(TelemetryHistory::SERIES_TEMPERATURE,
|
|
"7", reply, sizeof(reply)));
|
|
const std::vector<uint8_t> payload = decodeReply(reply);
|
|
ASSERT_EQ(61U, payload.size());
|
|
EXPECT_EQ((first_bucket + 1U) * TelemetryHistory::SAMPLE_INTERVAL_SECONDS,
|
|
uint32LE(&payload[1]));
|
|
|
|
size_t status_offset = 0;
|
|
EXPECT_EQ(TelemetryHistory::TEMPERATURE_VALUE,
|
|
readBits(&payload[7], status_offset, 2));
|
|
size_t bit_offset = 0;
|
|
EXPECT_EQ(50U, readBits(&payload[19], bit_offset, 7));
|
|
|
|
ASSERT_TRUE(history.formatPageReply(TelemetryHistory::SERIES_VOLTAGE,
|
|
"7", reply, sizeof(reply)));
|
|
const std::vector<uint8_t> voltage_payload = decodeReply(reply);
|
|
ASSERT_EQ(55U, voltage_payload.size());
|
|
EXPECT_EQ((first_bucket + 1U) * TelemetryHistory::SAMPLE_INTERVAL_SECONDS,
|
|
uint32LE(&voltage_payload[1]));
|
|
EXPECT_EQ(3U, voltage_payload[7]);
|
|
}
|
|
|
|
TEST(TelemetryHistory, FormatsMaximumRawVoltageSnapshot) {
|
|
using mesh::TelemetryHistory;
|
|
TelemetryHistory history;
|
|
const uint32_t first_bucket = 500000U;
|
|
const uint32_t total_samples = 200U;
|
|
for (uint32_t sample = 0; sample < total_samples; sample++) {
|
|
history.record((first_bucket + sample)
|
|
* TelemetryHistory::SAMPLE_INTERVAL_SECONDS,
|
|
20, true,
|
|
(uint16_t)(1880U + (sample % 253U) * 10U),
|
|
0, 0, false);
|
|
}
|
|
|
|
const uint8_t source_id[TelemetryHistory::BINARY_SOURCE_ID_SIZE] = {
|
|
0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF};
|
|
uint8_t payload[TelemetryHistory::BINARY_PAYLOAD_SIZE];
|
|
ASSERT_EQ(sizeof(payload), history.formatVoltageBinarySnapshot(
|
|
source_id, payload, sizeof(payload)));
|
|
EXPECT_EQ(0, memcmp(payload, "TVB1", 4));
|
|
EXPECT_EQ(0, memcmp(&payload[4], source_id, sizeof(source_id)));
|
|
|
|
const uint32_t oldest_sample =
|
|
total_samples - TelemetryHistory::BINARY_MAX_SAMPLES;
|
|
EXPECT_EQ((first_bucket + oldest_sample)
|
|
* TelemetryHistory::SAMPLE_INTERVAL_SECONDS,
|
|
uint32LE(&payload[12]));
|
|
EXPECT_EQ(30, uint16LE(&payload[16]));
|
|
EXPECT_EQ(TelemetryHistory::BINARY_MAX_SAMPLES, payload[18]);
|
|
for (uint16_t slot = 0;
|
|
slot < TelemetryHistory::BINARY_MAX_SAMPLES; slot++) {
|
|
const uint32_t sample = oldest_sample + slot;
|
|
EXPECT_EQ(TelemetryHistory::encodeVoltage(
|
|
(uint16_t)(1880U + (sample % 253U) * 10U)),
|
|
payload[TelemetryHistory::BINARY_HEADER_SIZE + slot]);
|
|
}
|
|
}
|
|
|
|
TEST(TelemetryHistory, FormatsMaximumRawTemperatureSnapshot) {
|
|
using mesh::TelemetryHistory;
|
|
TelemetryHistory history;
|
|
const uint32_t first_bucket = 700000U;
|
|
const uint32_t total_samples = TelemetryHistory::BINARY_MAX_SAMPLES;
|
|
for (uint32_t sample = 0; sample < total_samples; sample++) {
|
|
const int16_t temperature = sample == 0 ? -51
|
|
: sample == 1 ? 78 : (int16_t)(-50 + sample % 128U);
|
|
history.record((first_bucket + sample)
|
|
* TelemetryHistory::SAMPLE_INTERVAL_SECONDS,
|
|
temperature, sample != 2, 3700, 0, 0, false);
|
|
}
|
|
|
|
const uint8_t source_id[TelemetryHistory::BINARY_SOURCE_ID_SIZE] = {
|
|
0x10, 0x32, 0x54, 0x76, 0x98, 0xBA, 0xDC, 0xFE};
|
|
uint8_t payload[TelemetryHistory::BINARY_PAYLOAD_SIZE];
|
|
ASSERT_EQ(sizeof(payload), history.formatTemperatureBinarySnapshot(
|
|
source_id, payload, sizeof(payload)));
|
|
EXPECT_EQ(0, memcmp(payload, "TTB1", 4));
|
|
EXPECT_EQ(0, memcmp(&payload[4], source_id, sizeof(source_id)));
|
|
EXPECT_EQ(first_bucket * TelemetryHistory::SAMPLE_INTERVAL_SECONDS,
|
|
uint32LE(&payload[12]));
|
|
EXPECT_EQ(30, uint16LE(&payload[16]));
|
|
EXPECT_EQ(TelemetryHistory::BINARY_MAX_SAMPLES, payload[18]);
|
|
EXPECT_EQ(1, payload[TelemetryHistory::BINARY_HEADER_SIZE]);
|
|
EXPECT_EQ(2, payload[TelemetryHistory::BINARY_HEADER_SIZE + 1U]);
|
|
EXPECT_EQ(0, payload[TelemetryHistory::BINARY_HEADER_SIZE + 2U]);
|
|
for (uint16_t slot = 3; slot < TelemetryHistory::BINARY_MAX_SAMPLES; slot++) {
|
|
EXPECT_EQ((uint8_t)(slot % 128U + 3U),
|
|
payload[TelemetryHistory::BINARY_HEADER_SIZE + slot]);
|
|
}
|
|
}
|
|
|
|
TEST(TelemetryHistory, RawVoltageSnapshotRequiresHistoryAndDataSpace) {
|
|
using mesh::TelemetryHistory;
|
|
TelemetryHistory history;
|
|
const uint8_t source_id[TelemetryHistory::BINARY_SOURCE_ID_SIZE] = {};
|
|
uint8_t payload[TelemetryHistory::BINARY_PAYLOAD_SIZE];
|
|
|
|
EXPECT_EQ(0U, history.formatVoltageBinarySnapshot(
|
|
source_id, payload, sizeof(payload)));
|
|
history.record(1800000000U, 20, true, 3700, 0, 0, false);
|
|
EXPECT_EQ(0U, history.formatVoltageBinarySnapshot(
|
|
source_id, payload,
|
|
TelemetryHistory::BINARY_HEADER_SIZE));
|
|
EXPECT_EQ(TelemetryHistory::BINARY_HEADER_SIZE + 1U,
|
|
history.formatVoltageBinarySnapshot(
|
|
source_id, payload,
|
|
TelemetryHistory::BINARY_HEADER_SIZE + 1U));
|
|
EXPECT_EQ(1, payload[18]);
|
|
}
|
|
|
|
TEST(TelemetryHistory, EmitsZeroGpsOriginAndDeltasWithoutFixes) {
|
|
using mesh::TelemetryHistory;
|
|
TelemetryHistory history;
|
|
const uint32_t epoch = 1000000U * TelemetryHistory::SAMPLE_INTERVAL_SECONDS;
|
|
history.record(epoch, 20, true, 3700, 0, 0, false);
|
|
|
|
char reply[160];
|
|
ASSERT_TRUE(history.formatPageReply(TelemetryHistory::SERIES_GPS,
|
|
"1", reply, sizeof(reply)));
|
|
EXPECT_EQ(138U, strlen(reply));
|
|
const std::vector<uint8_t> payload = decodeReply(reply);
|
|
ASSERT_EQ(101U, payload.size());
|
|
EXPECT_EQ(TelemetryHistory::GPS_PAYLOAD_TYPE_V1, payload[0]);
|
|
EXPECT_EQ(0, int32LE(&payload[7]));
|
|
EXPECT_EQ(0, int32LE(&payload[11]));
|
|
EXPECT_EQ(0xFF, payload[15]);
|
|
for (size_t i = 17; i < payload.size(); i++) EXPECT_EQ(0, payload[i]);
|
|
}
|
|
|
|
TEST(TelemetryHistory, EncodesGpsAsTenMeterFourteenBitDeltas) {
|
|
using mesh::TelemetryHistory;
|
|
TelemetryHistory history;
|
|
const uint32_t first_bucket = 1000000U;
|
|
const uint32_t interval = TelemetryHistory::SAMPLE_INTERVAL_SECONDS;
|
|
const int32_t latitude = 470000000;
|
|
const int32_t longitude = -1220000000;
|
|
|
|
history.record(first_bucket * interval, 20, true, 3700,
|
|
latitude, longitude, true);
|
|
history.record((first_bucket + 1U) * interval, 20, true, 3700,
|
|
latitude + 8983, longitude, true); // about 100 m north
|
|
history.record((first_bucket + 2U) * interval, 20, true, 3700,
|
|
latitude + 8983, longitude + 13160, true); // about 100 m east
|
|
|
|
char reply[160];
|
|
ASSERT_TRUE(history.formatPageReply(TelemetryHistory::SERIES_GPS,
|
|
"1", reply, sizeof(reply)));
|
|
const std::vector<uint8_t> payload = decodeReply(reply);
|
|
ASSERT_EQ(101U, payload.size());
|
|
EXPECT_EQ(latitude, int32LE(&payload[7]));
|
|
EXPECT_EQ(longitude, int32LE(&payload[11]));
|
|
EXPECT_EQ(21, payload[15]);
|
|
EXPECT_EQ(0, payload[16]);
|
|
|
|
size_t bit_offset = 0;
|
|
for (int slot = 0; slot < 24; slot++) {
|
|
const int north = signed14(readBits(&payload[17], bit_offset, 14));
|
|
const int east = signed14(readBits(&payload[17], bit_offset, 14));
|
|
if (slot == 22) {
|
|
EXPECT_NEAR(10, north, 1);
|
|
EXPECT_NEAR(0, east, 1);
|
|
} else if (slot == 23) {
|
|
EXPECT_NEAR(0, north, 1);
|
|
EXPECT_NEAR(10, east, 1);
|
|
} else {
|
|
EXPECT_EQ(0, north);
|
|
EXPECT_EQ(0, east);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(TelemetryHistory, ExpandsGpsRetentionWithinHeapBudget) {
|
|
using mesh::TelemetryHistory;
|
|
TelemetryHistory history;
|
|
|
|
EXPECT_EQ(3, history.gpsRetentionDays());
|
|
EXPECT_EQ(6, history.gpsPageCount());
|
|
EXPECT_EQ(5, history.resizeGpsDays(
|
|
30, 5U * TelemetryHistory::GPS_HEAP_BYTES_PER_DAY));
|
|
EXPECT_EQ(10, history.gpsPageCount());
|
|
EXPECT_EQ(4, history.resizeGpsDays(4, 0));
|
|
EXPECT_EQ(8, history.gpsPageCount());
|
|
EXPECT_EQ(3, history.resizeGpsDays(3, 0));
|
|
EXPECT_EQ(6, history.gpsPageCount());
|
|
|
|
TelemetryHistory maximum;
|
|
EXPECT_EQ(30, maximum.resizeGpsDays(
|
|
30, 30U * TelemetryHistory::GPS_HEAP_BYTES_PER_DAY));
|
|
EXPECT_EQ(60, maximum.gpsPageCount());
|
|
}
|
|
|
|
TEST(TelemetryHistory, PreservesNewestGpsSamplesAcrossResizes) {
|
|
using mesh::TelemetryHistory;
|
|
TelemetryHistory history;
|
|
const uint32_t first_bucket = 1000000U;
|
|
const uint32_t interval = TelemetryHistory::SAMPLE_INTERVAL_SECONDS;
|
|
const int32_t latitude = 470000000;
|
|
const int32_t longitude = -1220000000;
|
|
|
|
for (uint32_t sample = 0; sample <= TelemetryHistory::GPS_RETENTION_SAMPLES;
|
|
sample++) {
|
|
history.record((first_bucket + sample) * interval, 20, true, 3700,
|
|
latitude + (int32_t)sample * 1000,
|
|
longitude, true);
|
|
}
|
|
ASSERT_EQ(7, history.resizeGpsDays(
|
|
7, 7U * TelemetryHistory::GPS_HEAP_BYTES_PER_DAY));
|
|
for (uint32_t sample = TelemetryHistory::GPS_RETENTION_SAMPLES + 1U;
|
|
sample <= 7U * TelemetryHistory::SAMPLES_PER_DAY; sample++) {
|
|
history.record((first_bucket + sample) * interval, 20, true, 3700,
|
|
latitude + (int32_t)sample * 1000,
|
|
longitude, true);
|
|
}
|
|
|
|
char reply[160];
|
|
ASSERT_TRUE(history.formatPageReply(TelemetryHistory::SERIES_GPS,
|
|
"14", reply, sizeof(reply)));
|
|
std::vector<uint8_t> payload = decodeReply(reply);
|
|
ASSERT_EQ(101U, payload.size());
|
|
EXPECT_EQ((first_bucket + 1U) * interval, uint32LE(&payload[1]));
|
|
EXPECT_EQ(latitude + 1000, int32LE(&payload[7]));
|
|
EXPECT_FALSE(history.formatPageReply(TelemetryHistory::SERIES_GPS,
|
|
"15", reply, sizeof(reply)));
|
|
EXPECT_STREQ("Err - telemetry.gps page must be 1-14", reply);
|
|
|
|
ASSERT_EQ(2, history.resizeGpsDays(2, 0));
|
|
ASSERT_TRUE(history.formatPageReply(TelemetryHistory::SERIES_GPS,
|
|
"4", reply, sizeof(reply)));
|
|
payload = decodeReply(reply);
|
|
ASSERT_EQ(101U, payload.size());
|
|
EXPECT_EQ((first_bucket + 241U) * interval, uint32LE(&payload[1]));
|
|
EXPECT_EQ(latitude + 241000, int32LE(&payload[7]));
|
|
}
|
|
|
|
TEST(TelemetryHistory, ValidatesRetentionAndPagingArguments) {
|
|
mesh::TelemetryHistory history;
|
|
history.record(1800000000U, 20, true, 3700, 0, 0, false);
|
|
char reply[160];
|
|
|
|
EXPECT_TRUE(history.formatPageReply(mesh::TelemetryHistory::SERIES_TEMPERATURE,
|
|
"7", reply, sizeof(reply)));
|
|
EXPECT_FALSE(history.formatPageReply(mesh::TelemetryHistory::SERIES_TEMPERATURE,
|
|
"8", reply, sizeof(reply)));
|
|
EXPECT_STREQ("Err - telemetry.temp page must be 1-7", reply);
|
|
EXPECT_FALSE(history.formatPageReply(mesh::TelemetryHistory::SERIES_VOLTAGE,
|
|
"0", reply, sizeof(reply)));
|
|
EXPECT_STREQ("Err - telemetry.volt page must be 1-7", reply);
|
|
EXPECT_TRUE(history.formatPageReply(mesh::TelemetryHistory::SERIES_GPS,
|
|
"6", reply, sizeof(reply)));
|
|
EXPECT_FALSE(history.formatPageReply(mesh::TelemetryHistory::SERIES_GPS,
|
|
"7", reply, sizeof(reply)));
|
|
EXPECT_STREQ("Err - telemetry.gps page must be 1-6", reply);
|
|
EXPECT_FALSE(history.formatPageReply(mesh::TelemetryHistory::SERIES_GPS,
|
|
"1 extra", reply, sizeof(reply)));
|
|
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();
|
|
}
|