137 lines
5.2 KiB
C++
137 lines
5.2 KiB
C++
#include <doctest/doctest.h>
|
|
|
|
#include "fgc/Config.h"
|
|
#include "fgc/TelemetryParser.h"
|
|
#include "fgc/sensors/McuEnvSensor.h"
|
|
|
|
#include <cstdio>
|
|
#include <fstream>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
|
|
using namespace fgc;
|
|
|
|
namespace {
|
|
|
|
// Only latestEnv() matters here; the rest is the minimum the interface demands.
|
|
struct EnvOnlyMotor : IMotorController {
|
|
std::optional<McuEnvReading> env;
|
|
void start() override {}
|
|
void stop() override {}
|
|
void sendCommand(const std::string&) override {}
|
|
MotorTelemetry telemetry() override { return {}; }
|
|
std::string lastDump() override { return ""; }
|
|
std::string lastDiag() override { return ""; }
|
|
unsigned diagSeq() const override { return 0; }
|
|
bool connected() const override { return true; }
|
|
std::optional<McuEnvReading> latestEnv() const override { return env; }
|
|
};
|
|
|
|
McuEnvReading reading(int t, int h, bool ok, int fan, int rpm, long long at) {
|
|
McuEnvReading r;
|
|
r.temp_c = t; r.humidity = h; r.sensor_ok = ok; r.fan_duty = fan; r.fan_rpm = rpm;
|
|
r.epoch_ms = at;
|
|
return r;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("an ENV line from the firmware parses, keys in any order") {
|
|
auto r = parseEnvLine("ENV t=23 h=45 ok=1 fan=0 rpm=0");
|
|
REQUIRE(r);
|
|
CHECK(r->temp_c == 23);
|
|
CHECK(r->humidity == 45);
|
|
CHECK(r->sensor_ok);
|
|
CHECK(r->fan_duty == 0);
|
|
r = parseEnvLine("ENV rpm=1180 fan=255 ok=0 h=51 t=-4");
|
|
REQUIRE(r);
|
|
CHECK(r->temp_c == -4);
|
|
CHECK_FALSE(r->sensor_ok);
|
|
CHECK(r->fan_duty == 255);
|
|
CHECK(r->fan_rpm == 1180);
|
|
}
|
|
|
|
TEST_CASE("anything that is not a whole ENV line is not a reading") {
|
|
CHECK_FALSE(parseEnvLine("ST Y:A,1,2,0,0,0,0,S"));
|
|
CHECK_FALSE(parseEnvLine("DUMP ENV t=23 h=45 ok=1")); // the dump's copy, handled there
|
|
CHECK_FALSE(parseEnvLine("ENV t=23 h=45")); // no ok: cannot tell good from bad
|
|
CHECK_FALSE(parseEnvLine("ENV t=2x h=45 ok=1"));
|
|
}
|
|
|
|
TEST_CASE("the MCU sensor serves a fresh good reading, and nothing else") {
|
|
EnvOnlyMotor motor;
|
|
long long now = 1'000'000;
|
|
McuEnvSensor s(motor, [&] { return now; });
|
|
|
|
CHECK_FALSE(s.sample()); // no ENV line yet
|
|
motor.env = reading(24, 40, true, 0, 0, now);
|
|
auto v = s.sample();
|
|
REQUIRE(v);
|
|
CHECK(v->temp_c == doctest::Approx(24.0));
|
|
CHECK(v->humidity_pct == doctest::Approx(40.0));
|
|
CHECK(s.connected());
|
|
CHECK(s.name() == "DHT11");
|
|
|
|
SUBCASE("a failed read is not served as a reading") {
|
|
motor.env = reading(24, 40, false, 255, 0, now);
|
|
CHECK_FALSE(s.sample());
|
|
}
|
|
SUBCASE("a line older than 2.5 read periods is stale") {
|
|
now += McuEnvSensor::kStaleMs + 1;
|
|
CHECK_FALSE(s.sample());
|
|
CHECK_FALSE(s.connected());
|
|
}
|
|
}
|
|
|
|
TEST_CASE("a fan driven hard with no rpm looks stalled; idle or turning does not") {
|
|
CHECK(fanLooksStalled(reading(30, 40, true, 200, 0, 0)));
|
|
CHECK(fanLooksStalled(reading(0, 0, false, 255, 0, 0))); // the failsafe, not turning
|
|
CHECK_FALSE(fanLooksStalled(reading(20, 40, true, 0, 0, 0))); // off, as it should be
|
|
CHECK_FALSE(fanLooksStalled(reading(30, 40, true, 100, 0, 0))); // too slow to be sure
|
|
CHECK_FALSE(fanLooksStalled(reading(30, 40, true, 200, 900, 0)));
|
|
}
|
|
|
|
TEST_CASE("hardware.ini: cooling needs the DHT11, and only 'fan' or 'none'") {
|
|
const std::string cfg_path = "test_mcuenv_config.ini";
|
|
const std::string hw_path = "test_mcuenv_hardware.ini";
|
|
{
|
|
std::ofstream c(cfg_path);
|
|
c << "[General]\ntower_name = Legacy\n[Camera.rgb]\nid = X\nprofile_dir = /tmp/p\n";
|
|
}
|
|
auto write_hw = [&](const std::string& hardware, const std::string& pins) {
|
|
std::ofstream h(hw_path);
|
|
h << "[Board]\nmodel = lattepanda-3-delta\nfirmware_env = leonardo\n"
|
|
"[Hardware]\naxes = yaw\nimu = none\ncameras = rgb\n" << hardware <<
|
|
"[Pins]\nyaw_cs = 8\nyaw_en = 7\n" << pins;
|
|
};
|
|
|
|
SUBCASE("a legacy tower loads, with its MCU pins") {
|
|
write_hw("env = dht11\ncooling = fan\n",
|
|
"dht = 9\nfan_pwm = 10\nfan_tach = 11\nspi_idle_cs = 2\n");
|
|
auto c = ConfigLoader::loadFromFile(cfg_path, hw_path);
|
|
CHECK(c.hardware.env == "dht11");
|
|
CHECK(c.hardware.cooling == "fan");
|
|
CHECK(c.hardware.pins.dht == 9);
|
|
CHECK(c.hardware.pins.fan_tach == 11);
|
|
CHECK(c.hardware.pins.spi_idle_cs == 2);
|
|
}
|
|
SUBCASE("a fan without the DHT11 is refused") {
|
|
write_hw("env = sht41\ncooling = fan\n", "fan_pwm = 10\n");
|
|
CHECK_THROWS_WITH_AS(ConfigLoader::loadFromFile(cfg_path, hw_path),
|
|
doctest::Contains("needs Hardware.env = dht11"), std::runtime_error);
|
|
}
|
|
SUBCASE("an unknown cooling is refused") {
|
|
write_hw("env = dht11\ncooling = water\n", "dht = 9\n");
|
|
CHECK_THROWS_WITH_AS(ConfigLoader::loadFromFile(cfg_path, hw_path),
|
|
doctest::Contains("'none' or 'fan'"), std::runtime_error);
|
|
}
|
|
SUBCASE("towers without it are unchanged") {
|
|
write_hw("env = sht41\n", "");
|
|
auto c = ConfigLoader::loadFromFile(cfg_path, hw_path);
|
|
CHECK(c.hardware.cooling.empty());
|
|
CHECK(c.hardware.pins.dht == -1);
|
|
}
|
|
std::remove(cfg_path.c_str());
|
|
std::remove(hw_path.c_str());
|
|
}
|