Preserve exact LPP bytes in Sensor telemetry snapshots
This commit is contained in:
1 parent
dcfa31bb50
commit
fc91b6e75c
3 files changed
+109
-7
No files matched your search
@@ -107,7 +107,7 @@ static File openAppend(FILESYSTEM* _fs, const char* fname) {
|
||||
|
||||
/* --------------------- Cayenne LPP helpers ----------------------------*/
|
||||
|
||||
static float findTelemValue(const uint8_t* buf, uint8_t size, uint8_t channel, uint8_t type, float def_value) {
|
||||
static const uint8_t* findTelemData(const uint8_t* buf, uint8_t size, uint8_t channel, uint8_t type) {
|
||||
uint8_t i = 0;
|
||||
|
||||
while (i + 2 < size) {
|
||||
@@ -119,11 +119,17 @@ static float findTelemValue(const uint8_t* buf, uint8_t size, uint8_t channel, u
|
||||
if (sz > size - i) break;
|
||||
|
||||
if (ch == channel && t == type) {
|
||||
return LPPData::getFloat(&buf[i], sz, LPPData::getMultiplier(t), LPPData::isSigned(t));
|
||||
return &buf[i];
|
||||
}
|
||||
i += sz; // skip
|
||||
}
|
||||
return def_value; // not found
|
||||
return nullptr; // not found
|
||||
}
|
||||
|
||||
static float findTelemValue(const uint8_t* buf, uint8_t size, uint8_t channel, uint8_t type, float def_value) {
|
||||
const uint8_t* data = findTelemData(buf, size, channel, type);
|
||||
return data ? LPPData::getFloat(data, LPPData::getDataSize(type),
|
||||
LPPData::getMultiplier(type), LPPData::isSigned(type)) : def_value;
|
||||
}
|
||||
|
||||
/* ------------------ end Cayenne LPP helpers ----------------------*/
|
||||
@@ -166,9 +172,11 @@ void SensorMesh::snapshotTelemetry(ClientInfo* c) {
|
||||
|
||||
c->extra.sensor.prev_telem[i] = ch;
|
||||
c->extra.sensor.prev_telem[i + 1] = t;
|
||||
float v = findTelemValue(buf, size, ch, t, 0.0f);
|
||||
LPPData::putFloat(&c->extra.sensor.prev_telem[i + 2], v, sz,
|
||||
LPPData::getMultiplier(t), LPPData::isSigned(t));
|
||||
// Retain exactly what was delivered; a float round trip can lose one LSB
|
||||
// and repeatedly report the same unchanged reading as a new delta.
|
||||
const uint8_t* value = findTelemData(buf, size, ch, t);
|
||||
if (value) memcpy(&c->extra.sensor.prev_telem[i + 2], value, sz);
|
||||
else memset(&c->extra.sensor.prev_telem[i + 2], 0, sz);
|
||||
i += 2 + sz;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,6 +247,82 @@ static void tracker_mode_checks() {
|
||||
assert(target.allocations == 2 && target.queued == 1);
|
||||
}
|
||||
}
|
||||
static void snapshot_checks() {
|
||||
for (uint8_t stored_path : {0, 0xFF}) {
|
||||
for (unsigned refusal = 0; refusal < 3; ++refusal) {
|
||||
SensorMesh target;
|
||||
target.acl.clients.resize(1);
|
||||
ClientInfo& client = target.acl.clients[0];
|
||||
client.out_path_len = stored_path;
|
||||
assert(subscribe(target, client, LPP_VOLTAGE, 2) == 12);
|
||||
target.telemetry.bytes = {1, LPP_VOLTAGE, 1, 164}; // Delivered 4.20 V.
|
||||
target.refuse_allocation = refusal == 1;
|
||||
target.refuse_queue = refusal == 2;
|
||||
target.pushSubscriptions();
|
||||
if (refusal) {
|
||||
assert(!target.queued && client.extra.sensor.prev_telem[2] == 0);
|
||||
assert(client.extra.sensor.prev_telem[3] == 0 && target.telemHasChanged(&client));
|
||||
target.refuse_allocation = target.refuse_queue = false;
|
||||
target.pushSubscriptions();
|
||||
}
|
||||
assert(!memcmp(client.extra.sensor.prev_telem, target.telemetry.bytes.data(), 4)
|
||||
&& "snapshot must preserve the delivered LPP bytes");
|
||||
assert(target.queued == 1 && !target.telemHasChanged(&client));
|
||||
for (unsigned repeat = 0; repeat < 10; ++repeat) target.pushSubscriptions();
|
||||
assert(target.queued == 1 && !target.telemHasChanged(&client));
|
||||
}
|
||||
}
|
||||
uint32_t samples = 0;
|
||||
for (unsigned type = 0; type < 256; ++type) {
|
||||
if (!LPPData::isScalarType(type)) continue;
|
||||
SensorMesh target;
|
||||
ClientInfo client;
|
||||
const uint8_t size = LPPData::getDataSize(type);
|
||||
assert(subscribe(target, client, type, size) == 12);
|
||||
// Exhaust every one-/two-byte encoding, including signed/scaled data.
|
||||
// Four-byte samples straddle float's exact-integer range and sign limits.
|
||||
const std::vector<uint32_t> wide = {0, 1, 0xffff, 0x10000, 0xffffff,
|
||||
0x1000000, 0x1000001, 0x7fffffff, 0x80000000, 0xfffffffe, 0xffffffff};
|
||||
const uint32_t count = size < 4 ? uint32_t(1) << (8 * size) : wide.size();
|
||||
target.telemetry.bytes.resize(2 + size);
|
||||
target.telemetry.bytes[0] = 1;
|
||||
target.telemetry.bytes[1] = type;
|
||||
for (uint32_t index = 0; index < count; ++index) {
|
||||
uint32_t raw = size < 4 ? index : wide[index];
|
||||
for (unsigned byte = size; byte > 0; --byte) {
|
||||
target.telemetry.bytes[1 + byte] = uint8_t(raw);
|
||||
raw >>= 8;
|
||||
}
|
||||
memset(client.extra.sensor.prev_telem, 0xa5, sizeof(client.extra.sensor.prev_telem));
|
||||
target.snapshotTelemetry(&client);
|
||||
assert(!memcmp(client.extra.sensor.prev_telem, target.telemetry.bytes.data(), 2 + size)
|
||||
&& "snapshot must preserve the delivered LPP bytes");
|
||||
assert(!target.telemHasChanged(&client));
|
||||
++samples;
|
||||
}
|
||||
}
|
||||
SensorMesh target;
|
||||
ClientInfo client;
|
||||
assert(subscribe(target, client, LPP_VOLTAGE, 2) == 12);
|
||||
target.telemetry.bytes = {2, LPP_RELATIVE_HUMIDITY, 127, 1, LPP_VOLTAGE, 1, 164,
|
||||
1, LPP_VOLTAGE, 1, 194};
|
||||
target.snapshotTelemetry(&client);
|
||||
assert(client.extra.sensor.prev_telem[2] == 1 && client.extra.sensor.prev_telem[3] == 164);
|
||||
assert(findTelemValue(target.telemetry.getBuffer(), target.telemetry.getSize(),
|
||||
1, LPP_VOLTAGE, -99) == LPPData::getFloat(target.telemetry.getBuffer() + 5, 2, 100, true));
|
||||
// Missing/truncated records keep the existing zero baseline and lookup default.
|
||||
const auto original = target.telemetry.bytes;
|
||||
for (unsigned length = 0; length <= 6; ++length) {
|
||||
target.telemetry.bytes.assign(original.begin(), original.begin() + length);
|
||||
memset(client.extra.sensor.prev_telem, 0xa5, sizeof(client.extra.sensor.prev_telem));
|
||||
target.snapshotTelemetry(&client);
|
||||
assert(client.extra.sensor.prev_telem[0] == 1 && client.extra.sensor.prev_telem[1] == LPP_VOLTAGE);
|
||||
assert(client.extra.sensor.prev_telem[2] == 0 && client.extra.sensor.prev_telem[3] == 0);
|
||||
assert(findTelemValue(target.telemetry.getBuffer(), target.telemetry.getSize(),
|
||||
1, LPP_VOLTAGE, -99) == -99);
|
||||
}
|
||||
std::cout << "Raw LPP snapshot samples checked: " << samples << '\n';
|
||||
}
|
||||
static void writer_checks() {
|
||||
uint8_t buffer[16];
|
||||
memset(buffer, 0xAA, sizeof(buffer));
|
||||
@@ -355,6 +431,7 @@ int main(int argc, char** argv) {
|
||||
else if (!strcmp(argv[1], "capacity")) capacity_checks();
|
||||
else if (!strcmp(argv[1], "writer")) writer_checks();
|
||||
else if (!strcmp(argv[1], "tracker")) tracker_mode_checks();
|
||||
else if (!strcmp(argv[1], "snapshot")) snapshot_checks();
|
||||
else assert(false);
|
||||
std::cout << "Sensor subscription checks passed\n";
|
||||
}
|
||||
@@ -17,7 +17,8 @@ FIXTURE = ROOT / 'test/fixtures/sensor_subscriptions/test_sensor_subscriptions.c
|
||||
|
||||
|
||||
def production_methods(source):
|
||||
methods = extract_braced(source, 'static float findTelemValue(') + '\n'
|
||||
methods = extract_braced(source, 'static const uint8_t* findTelemData(') + '\n'
|
||||
methods += extract_braced(source, 'static float findTelemValue(') + '\n'
|
||||
methods += extract_braced(source, 'bool SensorMesh::telemHasChanged(') + '\n'
|
||||
if 'void SensorMesh::snapshotTelemetry(' in source:
|
||||
methods += extract_braced(source, 'void SensorMesh::snapshotTelemetry(') + '\n'
|
||||
@@ -74,6 +75,22 @@ class SensorSubscriptionTest(unittest.TestCase):
|
||||
def test_allocation_and_queue_failures_keep_delta_available_for_retry(self):
|
||||
self.passed('admission')
|
||||
|
||||
def test_raw_snapshots_preserve_scalars_and_unchanged_pushes_stop(self):
|
||||
self.passed('snapshot')
|
||||
|
||||
def test_lossy_float_snapshot_negative_control(self):
|
||||
def old(source):
|
||||
current = ''' const uint8_t* value = findTelemData(buf, size, ch, t);
|
||||
if (value) memcpy(&c->extra.sensor.prev_telem[i + 2], value, sz);
|
||||
else memset(&c->extra.sensor.prev_telem[i + 2], 0, sz);'''
|
||||
self.assertEqual(source.count(current), 1)
|
||||
return source.replace(current, ''' float v = findTelemValue(buf, size, ch, t, 0.0f);
|
||||
LPPData::putFloat(&c->extra.sensor.prev_telem[i + 2], v, sz,
|
||||
LPPData::getMultiplier(t), LPPData::isSigned(t));''', 1)
|
||||
result = self.execute('snapshot', old)
|
||||
self.assertNotEqual(result.returncode, 0, 'lossy snapshot escaped regression')
|
||||
self.assertIn('snapshot must preserve the delivered LPP bytes', result.stderr)
|
||||
|
||||
def test_subscription_collection_and_push_fit_host_packet_budget(self):
|
||||
self.passed('capacity')
|
||||
|
||||
|
||||
Reference in new issue
Block a user