mirror of
https://github.com/meshtastic/firmware.git
synced 2026-11-08 23:34:41 -05:00
487 lines
17 KiB
C++
487 lines
17 KiB
C++
// Unit tests for the Modbus-RTU protocol code in src/modules/SerialModbus.{h,cpp}, used by SerialModule's
|
|
// MODBUS mode to poll a SenseCAP ONE weather sensor on RS485 and to tunnel the client API over the same bus.
|
|
//
|
|
// What is pinned, and why:
|
|
// - crc16() / buildRead() / checkResponse() against frames whose CRCs were recomputed with a reference
|
|
// CRC-16/Modbus (poly 0xA001 reflected, init 0xFFFF). A wrong CRC or byte order makes every poll fail
|
|
// on real hardware while the code still looks plausible. Three response examples in the Seeed manual
|
|
// carry wrong CRCs; the vectors here are the recomputed ones.
|
|
// - checkResponse() must reject a frame with a bad CRC, a foreign address, or the wrong register count,
|
|
// and must report a partial frame as RESP_INCOMPLETE rather than RESP_INVALID: SerialModule polls it every 10 ms
|
|
// while bytes are still arriving, and a host tunnel frame on the shared bus must never be decoded as a
|
|
// sensor reading.
|
|
// - storeRegisters() decodes big-endian signed int32 register pairs; a sign or byte-order slip turns
|
|
// -1.0 C into a huge positive value.
|
|
// - Aggregate scales raw milli-units to telemetry units (pressure Pa*1000 -> hPa) and takes the vector
|
|
// mean of wind direction. An arithmetic mean of 350 and 10 degrees gives 180, the opposite direction.
|
|
// The mean uses polynomial sin/atan2 (libm's cost 4 KB of STM32 flash); a whole degree in must come
|
|
// back as the same degree, or the approximation has drifted.
|
|
// - Tunnel implements the provisioning tunnel (function codes 0x41 write / 0x42 read, address 240):
|
|
// a retained API frame larger than one read must arrive complete over several reads, a host retry
|
|
// (same seq and function code) must get the identical answer without advancing the output or applying
|
|
// input twice, and frames for other addresses must be ignored. Losing or duplicating bytes here
|
|
// corrupts the 0x94C3 StreamAPI framing the stock client relies on.
|
|
#include "TestUtil.h"
|
|
#include <unity.h>
|
|
|
|
#if !MESHTASTIC_EXCLUDE_MODBUS
|
|
|
|
#include "modules/SerialModbus.h"
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <vector>
|
|
|
|
using namespace modbus;
|
|
|
|
static std::vector<uint8_t> hex(const char *s)
|
|
{
|
|
std::vector<uint8_t> out;
|
|
unsigned v;
|
|
int n;
|
|
while (sscanf(s, " %2x%n", &v, &n) == 1) {
|
|
out.push_back((uint8_t)v);
|
|
s += n;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
static void assertCrc(const char *frame)
|
|
{
|
|
std::vector<uint8_t> f = hex(frame);
|
|
uint16_t crc = crc16(f.data(), f.size() - 2);
|
|
TEST_ASSERT_EQUAL_HEX8_MESSAGE(crc & 0xFF, f[f.size() - 2], frame);
|
|
TEST_ASSERT_EQUAL_HEX8_MESSAGE(crc >> 8, f[f.size() - 1], frame);
|
|
}
|
|
|
|
static void assertRequest(const char *frame, uint8_t addr, uint16_t reg, uint16_t count)
|
|
{
|
|
std::vector<uint8_t> want = hex(frame);
|
|
uint8_t out[8];
|
|
TEST_ASSERT_EQUAL(8, buildRead(out, addr, READ_INPUT, reg, count));
|
|
TEST_ASSERT_EQUAL_HEX8_ARRAY_MESSAGE(want.data(), out, 8, frame);
|
|
}
|
|
|
|
void setUp(void) {}
|
|
void tearDown(void) {}
|
|
|
|
// --- CRC and frame builder ---
|
|
|
|
void test_crc_reference_vectors()
|
|
{
|
|
const char *frames[] = {"01 04 00 00 00 02 71 CB",
|
|
"01 04 04 00 00 6E 8C D6 41",
|
|
"01 04 04 FF FF FC 18 BA AA",
|
|
"0A 04 00 00 00 06 71 73",
|
|
"0A 04 00 08 00 0C 70 B6",
|
|
"0A 04 18 00 00 00 00 00 03 6E 84 00 03 C8 C0 00 00 00 00 00 00 04 BC 00 00 02 10 BC 78",
|
|
"14 04 00 00 00 20 F3 17",
|
|
"2B 04 00 00 00 20 F6 18",
|
|
"2B 04 00 30 00 04 F6 0C",
|
|
"2B 04 00 40 00 02 77 D5",
|
|
"2B 04 04 00 0C EC 98 FD 2F",
|
|
"01 84 01 82 C0",
|
|
"01 84 04 42 C3",
|
|
"F0 42 00 41 53",
|
|
"F0 42 00 00 93 30",
|
|
"F0 42 01 80 93",
|
|
"F0 41 05 06 94 C3 00 02 18 01 DD 6D",
|
|
"F0 41 05 06 E0 62",
|
|
"F0 C1 01 E1 A3"};
|
|
for (const char *f : frames)
|
|
assertCrc(f);
|
|
}
|
|
|
|
void test_build_read_requests()
|
|
{
|
|
assertRequest("01 04 00 00 00 02 71 CB", 1, 0x0000, 2);
|
|
assertRequest("0A 04 00 00 00 06 71 73", 10, 0x0000, 6);
|
|
assertRequest("0A 04 00 08 00 0C 70 B6", 10, 0x0008, 12);
|
|
assertRequest("14 04 00 00 00 20 F3 17", 20, 0x0000, 0x20);
|
|
assertRequest("2B 04 00 00 00 20 F6 18", 43, 0x0000, 0x20);
|
|
assertRequest("2B 04 00 30 00 04 F6 0C", 43, 0x0030, 4);
|
|
assertRequest("2B 04 00 40 00 02 77 D5", 43, 0x0040, 2);
|
|
}
|
|
|
|
// --- Response validation and register decode ---
|
|
|
|
void test_temperature_responses_decode_signed()
|
|
{
|
|
int32_t raw[REG_SLOTS] = {};
|
|
std::vector<uint8_t> pos = hex("01 04 04 00 00 6E 8C D6 41");
|
|
TEST_ASSERT_EQUAL(RESP_OK, checkResponse(pos.data(), pos.size(), 1, READ_INPUT, 2));
|
|
storeRegisters(raw, 0, pos.data() + 3, pos[2]);
|
|
TEST_ASSERT_EQUAL_INT32(28300, raw[0]);
|
|
|
|
std::vector<uint8_t> neg = hex("01 04 04 FF FF FC 18 BA AA");
|
|
TEST_ASSERT_EQUAL(RESP_OK, checkResponse(neg.data(), neg.size(), 1, READ_INPUT, 2));
|
|
storeRegisters(raw, 0, neg.data() + 3, neg[2]);
|
|
TEST_ASSERT_EQUAL_INT32(-1000, raw[0]);
|
|
}
|
|
|
|
void test_s500_wind_block_decodes()
|
|
{
|
|
int32_t raw[REG_SLOTS] = {};
|
|
std::vector<uint8_t> f = hex("0A 04 18 00 00 00 00 00 03 6E 84 00 03 C8 C0 00 00 00 00 00 00 04 BC 00 00 02 10 BC 78");
|
|
TEST_ASSERT_EQUAL(RESP_OK, checkResponse(f.data(), f.size(), 10, READ_INPUT, 12));
|
|
storeRegisters(raw, 0x0008, f.data() + 3, f[2]);
|
|
TEST_ASSERT_EQUAL_INT32(0, raw[0x08 / 2]); // min direction
|
|
TEST_ASSERT_EQUAL_INT32(224900, raw[0x0A / 2]); // max direction
|
|
TEST_ASSERT_EQUAL_INT32(248000, raw[0x0C / 2]); // avg direction
|
|
TEST_ASSERT_EQUAL_INT32(0, raw[0x0E / 2]); // min speed
|
|
TEST_ASSERT_EQUAL_INT32(1212, raw[0x10 / 2]); // max speed
|
|
TEST_ASSERT_EQUAL_INT32(528, raw[0x12 / 2]); // avg speed
|
|
}
|
|
|
|
void test_co2_response_decodes()
|
|
{
|
|
int32_t raw[REG_SLOTS] = {};
|
|
std::vector<uint8_t> f = hex("2B 04 04 00 0C EC 98 FD 2F");
|
|
TEST_ASSERT_EQUAL(RESP_OK, checkResponse(f.data(), f.size(), 43, READ_INPUT, 2));
|
|
storeRegisters(raw, 0x0040, f.data() + 3, f[2]);
|
|
TEST_ASSERT_EQUAL_INT32(847000, raw[0x40 / 2]);
|
|
}
|
|
|
|
void test_exception_frames_reported()
|
|
{
|
|
std::vector<uint8_t> e1 = hex("01 84 01 82 C0");
|
|
std::vector<uint8_t> e4 = hex("01 84 04 42 C3");
|
|
TEST_ASSERT_EQUAL(RESP_EXCEPTION, checkResponse(e1.data(), e1.size(), 1, READ_INPUT, 2));
|
|
TEST_ASSERT_EQUAL(RESP_EXCEPTION, checkResponse(e4.data(), e4.size(), 1, READ_INPUT, 2));
|
|
}
|
|
|
|
void test_crc_mismatch_rejected()
|
|
{
|
|
std::vector<uint8_t> f = hex("01 04 04 00 00 6E 8C D6 41");
|
|
f[5] ^= 0x01;
|
|
TEST_ASSERT_EQUAL(RESP_INVALID, checkResponse(f.data(), f.size(), 1, READ_INPUT, 2));
|
|
}
|
|
|
|
void test_wrong_address_rejected()
|
|
{
|
|
// A tunnel frame for address 240 arriving while a sensor answer is awaited.
|
|
std::vector<uint8_t> f = hex("F0 42 00 41 53");
|
|
TEST_ASSERT_EQUAL(RESP_INVALID, checkResponse(f.data(), f.size(), 1, READ_INPUT, 2));
|
|
TEST_ASSERT_EQUAL(RESP_INVALID, checkResponse(f.data(), 1, 1, READ_INPUT, 2));
|
|
}
|
|
|
|
void test_wrong_register_count_rejected()
|
|
{
|
|
std::vector<uint8_t> f = hex("01 04 04 00 00 6E 8C D6 41");
|
|
TEST_ASSERT_EQUAL(RESP_INVALID, checkResponse(f.data(), f.size(), 1, READ_INPUT, 4));
|
|
}
|
|
|
|
void test_short_frame_incomplete()
|
|
{
|
|
std::vector<uint8_t> f = hex("01 04 04 00 00 6E 8C D6 41");
|
|
for (size_t len = 0; len < f.size(); len++)
|
|
TEST_ASSERT_EQUAL_MESSAGE(RESP_INCOMPLETE, checkResponse(f.data(), len, 1, READ_INPUT, 2), "partial frame");
|
|
}
|
|
|
|
// --- Profiles and poll plan ---
|
|
|
|
void test_unknown_address_gets_s700_set()
|
|
{
|
|
TEST_ASSERT_EQUAL_HEX8(CAP_THP | CAP_LIGHT | CAP_WIND | CAP_RAIN, capsForAddress(5));
|
|
TEST_ASSERT_EQUAL_HEX8(CAP_WIND, capsForAddress(44));
|
|
}
|
|
|
|
void test_poll_plan_full_and_split()
|
|
{
|
|
uint8_t step = 0;
|
|
uint16_t reg, count;
|
|
uint8_t s1000 = capsForAddress(43);
|
|
TEST_ASSERT_TRUE(nextRead(s1000, false, step, reg, count));
|
|
TEST_ASSERT_EQUAL(0x00, reg);
|
|
TEST_ASSERT_EQUAL(0x20, count);
|
|
step++;
|
|
TEST_ASSERT_TRUE(nextRead(s1000, false, step, reg, count));
|
|
TEST_ASSERT_EQUAL(0x30, reg);
|
|
step++;
|
|
TEST_ASSERT_TRUE(nextRead(s1000, false, step, reg, count));
|
|
TEST_ASSERT_EQUAL(0x40, reg);
|
|
step++;
|
|
TEST_ASSERT_FALSE(nextRead(s1000, false, step, reg, count));
|
|
|
|
step = 0;
|
|
uint8_t s500 = capsForAddress(10);
|
|
TEST_ASSERT_TRUE(nextRead(s500, true, step, reg, count));
|
|
TEST_ASSERT_EQUAL(0x00, reg);
|
|
TEST_ASSERT_EQUAL(6, count);
|
|
step++;
|
|
TEST_ASSERT_TRUE(nextRead(s500, true, step, reg, count));
|
|
TEST_ASSERT_EQUAL(0x08, reg);
|
|
TEST_ASSERT_EQUAL(12, count);
|
|
step++;
|
|
TEST_ASSERT_FALSE(nextRead(s500, true, step, reg, count));
|
|
}
|
|
|
|
// --- Aggregation ---
|
|
|
|
void test_pressure_scaled_to_hpa()
|
|
{
|
|
int32_t raw[REG_SLOTS] = {};
|
|
raw[0x04 / 2] = 101160000;
|
|
Aggregate agg;
|
|
agg.add(raw);
|
|
meshtastic_EnvironmentMetrics e = meshtastic_EnvironmentMetrics_init_zero;
|
|
agg.environment(e, CAP_THP);
|
|
TEST_ASSERT_TRUE(e.has_barometric_pressure);
|
|
TEST_ASSERT_FLOAT_WITHIN(0.01f, 1011.6f, e.barometric_pressure);
|
|
}
|
|
|
|
void test_wind_direction_vector_mean_wraps()
|
|
{
|
|
int32_t raw[REG_SLOTS] = {};
|
|
Aggregate agg;
|
|
raw[0x0C / 2] = 350000;
|
|
agg.add(raw);
|
|
raw[0x0C / 2] = 10000;
|
|
agg.add(raw);
|
|
meshtastic_EnvironmentMetrics e = meshtastic_EnvironmentMetrics_init_zero;
|
|
agg.environment(e, CAP_WIND);
|
|
TEST_ASSERT_TRUE(e.has_wind_direction);
|
|
TEST_ASSERT_EQUAL_UINT16(0, e.wind_direction);
|
|
}
|
|
|
|
void test_wind_direction_single_sample_round_trips()
|
|
{
|
|
// The mean uses polynomial sin/atan2 instead of libm; every whole degree must come back unchanged.
|
|
int32_t raw[REG_SLOTS] = {};
|
|
for (uint16_t deg = 0; deg < 360; deg++) {
|
|
Aggregate agg;
|
|
raw[0x0C / 2] = deg * 1000;
|
|
agg.add(raw);
|
|
meshtastic_EnvironmentMetrics e = meshtastic_EnvironmentMetrics_init_zero;
|
|
agg.environment(e, CAP_WIND);
|
|
TEST_ASSERT_EQUAL_UINT16(deg, e.wind_direction);
|
|
}
|
|
}
|
|
|
|
void test_wind_gust_and_lull_are_extremes()
|
|
{
|
|
int32_t raw[REG_SLOTS] = {};
|
|
Aggregate agg;
|
|
raw[0x0E / 2] = 500; // min speed
|
|
raw[0x10 / 2] = 3000;
|
|
raw[0x12 / 2] = 1000;
|
|
agg.add(raw);
|
|
raw[0x0E / 2] = 200;
|
|
raw[0x10 / 2] = 2000;
|
|
raw[0x12 / 2] = 2000;
|
|
agg.add(raw);
|
|
meshtastic_EnvironmentMetrics e = meshtastic_EnvironmentMetrics_init_zero;
|
|
agg.environment(e, CAP_WIND);
|
|
TEST_ASSERT_FLOAT_WITHIN(0.001f, 0.2f, e.wind_lull);
|
|
TEST_ASSERT_FLOAT_WITHIN(0.001f, 3.0f, e.wind_gust);
|
|
TEST_ASSERT_FLOAT_WITHIN(0.001f, 1.5f, e.wind_speed);
|
|
}
|
|
|
|
void test_rain_24h_survives_counter_reset()
|
|
{
|
|
// Accumulated counter 10.0 -> 12.5 mm, then the sensor restarts from 0 and reaches 1.0 mm.
|
|
int32_t raw[REG_SLOTS] = {};
|
|
Aggregate agg;
|
|
raw[0x14 / 2] = 10000;
|
|
agg.add(raw);
|
|
raw[0x14 / 2] = 12500;
|
|
agg.add(raw);
|
|
agg.nextHour();
|
|
raw[0x14 / 2] = 0;
|
|
agg.add(raw);
|
|
raw[0x14 / 2] = 1000;
|
|
agg.add(raw);
|
|
meshtastic_EnvironmentMetrics e = meshtastic_EnvironmentMetrics_init_zero;
|
|
agg.environment(e, CAP_RAIN);
|
|
TEST_ASSERT_FLOAT_WITHIN(0.001f, 3.5f, e.rainfall_24h);
|
|
}
|
|
|
|
// --- API tunnel ---
|
|
|
|
static const char *const writeFrame = "F0 41 05 06 94 C3 00 02 18 01 DD 6D";
|
|
|
|
void test_tunnel_reference_frames()
|
|
{
|
|
Tunnel t(240);
|
|
uint8_t resp[256];
|
|
const uint8_t *in;
|
|
size_t inLen;
|
|
|
|
std::vector<uint8_t> read0 = hex("F0 42 00 41 53");
|
|
std::vector<uint8_t> empty = hex("F0 42 00 00 93 30");
|
|
TEST_ASSERT_EQUAL(empty.size(), t.handle(read0.data(), read0.size(), resp, in, inLen));
|
|
TEST_ASSERT_EQUAL_HEX8_ARRAY(empty.data(), resp, empty.size());
|
|
|
|
std::vector<uint8_t> w = hex(writeFrame);
|
|
std::vector<uint8_t> ack = hex("F0 41 05 06 E0 62");
|
|
TEST_ASSERT_EQUAL(ack.size(), t.handle(w.data(), w.size(), resp, in, inLen));
|
|
TEST_ASSERT_EQUAL_HEX8_ARRAY(ack.data(), resp, ack.size());
|
|
TEST_ASSERT_EQUAL(6, inLen);
|
|
TEST_ASSERT_EQUAL_HEX8_ARRAY(w.data() + 4, in, 6);
|
|
}
|
|
|
|
void test_tunnel_frame_length()
|
|
{
|
|
Tunnel t(240);
|
|
std::vector<uint8_t> w = hex(writeFrame);
|
|
TEST_ASSERT_EQUAL(0, t.frameLength(w.data(), 1));
|
|
TEST_ASSERT_EQUAL(0, t.frameLength(w.data(), 3)); // length byte not yet seen
|
|
TEST_ASSERT_EQUAL(w.size(), t.frameLength(w.data(), 4));
|
|
std::vector<uint8_t> r = hex("F0 42 01 80 93");
|
|
TEST_ASSERT_EQUAL(5, t.frameLength(r.data(), 2));
|
|
std::vector<uint8_t> other = hex("14 04 00 00 00 20 F3 17");
|
|
TEST_ASSERT_EQUAL(0, t.frameLength(other.data(), other.size()));
|
|
}
|
|
|
|
static size_t readSeq(Tunnel &t, uint8_t seq, uint8_t *resp)
|
|
{
|
|
uint8_t req[5] = {240, TUNNEL_READ, seq};
|
|
uint16_t crc = crc16(req, 3);
|
|
req[3] = crc & 0xFF;
|
|
req[4] = crc >> 8;
|
|
const uint8_t *in;
|
|
size_t inLen;
|
|
return t.handle(req, sizeof(req), resp, in, inLen);
|
|
}
|
|
|
|
void test_tunnel_large_frame_over_three_reads()
|
|
{
|
|
Tunnel t(240);
|
|
uint8_t frame[516];
|
|
for (size_t i = 0; i < sizeof(frame); i++)
|
|
frame[i] = (uint8_t)(i * 7 + 3);
|
|
t.frame = frame;
|
|
t.frameLen = sizeof(frame);
|
|
|
|
std::vector<uint8_t> got;
|
|
uint8_t resp[256];
|
|
const size_t expect[] = {240, 240, 36};
|
|
for (uint8_t seq = 1; seq <= 3; seq++) {
|
|
size_t n = readSeq(t, seq, resp);
|
|
TEST_ASSERT_EQUAL(6 + expect[seq - 1], n);
|
|
TEST_ASSERT_EQUAL(expect[seq - 1], resp[3]);
|
|
TEST_ASSERT_EQUAL_HEX16(crc16(resp, n - 2), resp[n - 2] | resp[n - 1] << 8);
|
|
got.insert(got.end(), resp + 4, resp + 4 + resp[3]);
|
|
}
|
|
TEST_ASSERT_NULL(t.frame); // released: the next API frame may be dequeued
|
|
TEST_ASSERT_EQUAL(sizeof(frame), got.size());
|
|
TEST_ASSERT_EQUAL_HEX8_ARRAY(frame, got.data(), sizeof(frame));
|
|
TEST_ASSERT_EQUAL(6, readSeq(t, 4, resp)); // nothing pending
|
|
}
|
|
|
|
void test_tunnel_read_retry_resends_without_advancing()
|
|
{
|
|
Tunnel t(240);
|
|
uint8_t frame[300];
|
|
for (size_t i = 0; i < sizeof(frame); i++)
|
|
frame[i] = (uint8_t)i;
|
|
t.frame = frame;
|
|
t.frameLen = sizeof(frame);
|
|
|
|
uint8_t first[256], again[256];
|
|
size_t n1 = readSeq(t, 9, first);
|
|
memcpy(again, first, n1); // the caller hands the same buffer back, as SerialModule does
|
|
size_t n2 = readSeq(t, 9, again);
|
|
TEST_ASSERT_EQUAL(n1, n2);
|
|
TEST_ASSERT_EQUAL_HEX8_ARRAY(first, again, n1);
|
|
|
|
uint8_t next[256];
|
|
memcpy(next, again, n2);
|
|
size_t n3 = readSeq(t, 10, next);
|
|
TEST_ASSERT_EQUAL(6 + 60, n3);
|
|
TEST_ASSERT_EQUAL_HEX8(240, next[4]); // continues at byte 240, not 480
|
|
TEST_ASSERT_NULL(t.frame);
|
|
}
|
|
|
|
void test_tunnel_repeated_write_applied_once()
|
|
{
|
|
Tunnel t(240);
|
|
std::vector<uint8_t> w = hex(writeFrame);
|
|
uint8_t resp[256];
|
|
const uint8_t *in;
|
|
size_t inLen;
|
|
size_t n1 = t.handle(w.data(), w.size(), resp, in, inLen);
|
|
TEST_ASSERT_EQUAL(6, inLen);
|
|
size_t n2 = t.handle(w.data(), w.size(), resp, in, inLen);
|
|
TEST_ASSERT_EQUAL(n1, n2);
|
|
TEST_ASSERT_EQUAL(0, inLen);
|
|
TEST_ASSERT_NULL(in);
|
|
}
|
|
|
|
void test_tunnel_ignores_other_address_and_bad_crc()
|
|
{
|
|
Tunnel t(240);
|
|
uint8_t resp[256];
|
|
const uint8_t *in;
|
|
size_t inLen;
|
|
std::vector<uint8_t> other = hex("01 04 00 00 00 02 71 CB");
|
|
TEST_ASSERT_EQUAL(0, t.handle(other.data(), other.size(), resp, in, inLen));
|
|
std::vector<uint8_t> bad = hex(writeFrame);
|
|
bad[6] ^= 0xFF;
|
|
TEST_ASSERT_EQUAL(0, t.handle(bad.data(), bad.size(), resp, in, inLen));
|
|
TEST_ASSERT_EQUAL(0, inLen);
|
|
}
|
|
|
|
void test_tunnel_unknown_function_gets_exception()
|
|
{
|
|
Tunnel t(240);
|
|
uint8_t req[4] = {240, 0x03};
|
|
uint16_t crc = crc16(req, 2);
|
|
req[2] = crc & 0xFF;
|
|
req[3] = crc >> 8;
|
|
uint8_t resp[256];
|
|
const uint8_t *in;
|
|
size_t inLen;
|
|
TEST_ASSERT_EQUAL(5, t.handle(req, sizeof(req), resp, in, inLen));
|
|
TEST_ASSERT_EQUAL_HEX8(240, resp[0]);
|
|
TEST_ASSERT_EQUAL_HEX8(0x83, resp[1]);
|
|
TEST_ASSERT_EQUAL_HEX8(0x01, resp[2]);
|
|
TEST_ASSERT_EQUAL_HEX16(crc16(resp, 3), resp[3] | resp[4] << 8);
|
|
}
|
|
|
|
void setup()
|
|
{
|
|
initializeTestEnvironment();
|
|
UNITY_BEGIN();
|
|
RUN_TEST(test_crc_reference_vectors);
|
|
RUN_TEST(test_build_read_requests);
|
|
RUN_TEST(test_temperature_responses_decode_signed);
|
|
RUN_TEST(test_s500_wind_block_decodes);
|
|
RUN_TEST(test_co2_response_decodes);
|
|
RUN_TEST(test_exception_frames_reported);
|
|
RUN_TEST(test_crc_mismatch_rejected);
|
|
RUN_TEST(test_wrong_address_rejected);
|
|
RUN_TEST(test_wrong_register_count_rejected);
|
|
RUN_TEST(test_short_frame_incomplete);
|
|
RUN_TEST(test_unknown_address_gets_s700_set);
|
|
RUN_TEST(test_poll_plan_full_and_split);
|
|
RUN_TEST(test_pressure_scaled_to_hpa);
|
|
RUN_TEST(test_wind_direction_vector_mean_wraps);
|
|
RUN_TEST(test_wind_direction_single_sample_round_trips);
|
|
RUN_TEST(test_wind_gust_and_lull_are_extremes);
|
|
RUN_TEST(test_rain_24h_survives_counter_reset);
|
|
RUN_TEST(test_tunnel_reference_frames);
|
|
RUN_TEST(test_tunnel_frame_length);
|
|
RUN_TEST(test_tunnel_large_frame_over_three_reads);
|
|
RUN_TEST(test_tunnel_read_retry_resends_without_advancing);
|
|
RUN_TEST(test_tunnel_repeated_write_applied_once);
|
|
RUN_TEST(test_tunnel_ignores_other_address_and_bad_crc);
|
|
RUN_TEST(test_tunnel_unknown_function_gets_exception);
|
|
exit(UNITY_END());
|
|
}
|
|
|
|
#else
|
|
|
|
void setUp(void) {}
|
|
void tearDown(void) {}
|
|
|
|
void setup()
|
|
{
|
|
initializeTestEnvironment();
|
|
UNITY_BEGIN();
|
|
exit(UNITY_END());
|
|
}
|
|
|
|
#endif
|
|
|
|
void loop() {}
|