108 lines
3.2 KiB
C++
108 lines
3.2 KiB
C++
#include "fgc/sensors/Sht41EnvSensor.h"
|
|
|
|
#include "fgc/Logger.h"
|
|
#include "fgc/sensors/I2cBus.h"
|
|
#include "fgc/sensors/Sht41Protocol.h"
|
|
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <mutex>
|
|
#include <thread>
|
|
|
|
namespace fgc {
|
|
|
|
namespace {
|
|
|
|
// The command bytes and the frame decode (CRC + raw->physical) live in
|
|
// Sht41Protocol, which is pure and unit-tested; this file owns only the I/O:
|
|
// bus access, poll timing, and the staleness rule.
|
|
constexpr auto kConversionDelay = std::chrono::milliseconds(10);
|
|
constexpr auto kSoftResetSettle = std::chrono::milliseconds(1);
|
|
|
|
} // namespace
|
|
|
|
struct Sht41EnvSensor::Impl {
|
|
Impl(std::string dev, uint8_t a, std::chrono::milliseconds p)
|
|
: bus(std::move(dev), a), period(p) {}
|
|
|
|
using clock = std::chrono::steady_clock;
|
|
|
|
I2cBus bus;
|
|
std::chrono::milliseconds period;
|
|
std::thread worker;
|
|
std::atomic<bool> running{false};
|
|
|
|
std::mutex mutex;
|
|
EnvSample latest;
|
|
clock::time_point last_ok{};
|
|
|
|
void readOnce() {
|
|
if (!bus.write({kSht41CmdMeasureHighPrec})) return;
|
|
std::this_thread::sleep_for(kConversionDelay);
|
|
std::vector<uint8_t> rx(kSht41ResponseBytes);
|
|
if (!bus.writeRead({}, rx)) return;
|
|
|
|
auto reading = decodeSht41Measurement(rx);
|
|
if (!reading) {
|
|
LOG_WARN << "SHT41: CRC mismatch, discarding sample";
|
|
return;
|
|
}
|
|
|
|
std::lock_guard<std::mutex> lock(mutex);
|
|
latest.valid = true;
|
|
latest.temp_c = reading->temp_c;
|
|
latest.humidity_pct = reading->humidity_pct;
|
|
latest.timestamp_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
std::chrono::system_clock::now().time_since_epoch())
|
|
.count();
|
|
last_ok = clock::now();
|
|
}
|
|
|
|
void run() {
|
|
while (running.load()) {
|
|
readOnce();
|
|
std::this_thread::sleep_for(period);
|
|
}
|
|
}
|
|
};
|
|
|
|
Sht41EnvSensor::Sht41EnvSensor(std::string i2c_device, uint8_t addr,
|
|
std::chrono::milliseconds period)
|
|
: impl_(std::make_unique<Impl>(std::move(i2c_device), addr, period)) {}
|
|
|
|
Sht41EnvSensor::~Sht41EnvSensor() { stop(); }
|
|
|
|
void Sht41EnvSensor::start() {
|
|
if (!impl_->bus.open()) {
|
|
LOG_ERROR << "SHT41: failed to open I2C bus; sensor disabled";
|
|
return;
|
|
}
|
|
impl_->bus.write({kSht41CmdSoftReset});
|
|
std::this_thread::sleep_for(kSoftResetSettle);
|
|
impl_->running = true;
|
|
impl_->worker = std::thread([this] { impl_->run(); });
|
|
LOG_INFO << "SHT41 env sensor started";
|
|
}
|
|
|
|
void Sht41EnvSensor::stop() {
|
|
if (!impl_) return;
|
|
impl_->running = false;
|
|
if (impl_->worker.joinable()) impl_->worker.join();
|
|
impl_->bus.close();
|
|
}
|
|
|
|
bool Sht41EnvSensor::connected() const {
|
|
std::lock_guard<std::mutex> lock(impl_->mutex);
|
|
return impl_->latest.valid &&
|
|
(Impl::clock::now() - impl_->last_ok) < 2 * impl_->period;
|
|
}
|
|
|
|
std::optional<EnvSample> Sht41EnvSensor::sample() {
|
|
std::lock_guard<std::mutex> lock(impl_->mutex);
|
|
if (!impl_->latest.valid || (Impl::clock::now() - impl_->last_ok) >= 2 * impl_->period)
|
|
return std::nullopt;
|
|
return impl_->latest;
|
|
}
|
|
|
|
} // namespace fgc
|