diff --git a/include/fgc/sensors/I2cBus.h b/include/fgc/sensors/I2cBus.h index 1427fd0..88db68e 100644 --- a/include/fgc/sensors/I2cBus.h +++ b/include/fgc/sensors/I2cBus.h @@ -28,6 +28,11 @@ public: bool writeRead(const std::vector& tx, std::vector& rx); bool write(const std::vector& tx); + // For diagnostics: what this bus is actually pointed at. A silent sensor is + // almost always the wrong bus or address, so error messages should name both. + const std::string& device() const { return device_; } + uint8_t address() const { return addr_; } + private: std::string device_; uint8_t addr_; diff --git a/src/sensors/Sht41EnvSensor.cpp b/src/sensors/Sht41EnvSensor.cpp index 2927e39..024e44d 100644 --- a/src/sensors/Sht41EnvSensor.cpp +++ b/src/sensors/Sht41EnvSensor.cpp @@ -5,6 +5,7 @@ #include "fgc/sensors/Sht41Protocol.h" #include +#include #include #include #include @@ -36,11 +37,37 @@ struct Sht41EnvSensor::Impl { EnvSample latest; clock::time_point last_ok{}; + // Consecutive failed reads. The transfers run every `period` forever, so the + // failure is logged on the FIRST one only: a wrong bus would otherwise print + // the same line every couple of seconds for the life of the process. Reset on + // a good read, which also gives us a recovery message. + long long fail_streak = 0; + + // A silent sensor is nearly always the wrong bus or address, and neither + // open() nor the I2C_SLAVE ioctl can tell you that - the ioctl only records + // the target address, it never probes. This is the first point where the + // absence of a device is actually observable, so it is where it gets said. + void reportFailure(const char* what) { + if (fail_streak++ > 0) return; + LOG_ERROR << "SHT41: " << what << " at address 0x" << std::hex + << static_cast(bus.address()) << std::dec << " on " << bus.device() + << " - check [Env] i2c_device/i2c_addr and the wiring" + << " (probe with: i2cdetect -l, then i2ctransfer -y w1@0x" + << std::hex << static_cast(bus.address()) << std::dec << " 0x89)"; + LOG_ERROR << "SHT41: further read failures will not be logged until it responds"; + } + void readOnce() { - if (!bus.write({kSht41CmdMeasureHighPrec})) return; + if (!bus.write({kSht41CmdMeasureHighPrec})) { + reportFailure("no response to the measure command"); + return; + } std::this_thread::sleep_for(kConversionDelay); std::vector rx(kSht41ResponseBytes); - if (!bus.writeRead({}, rx)) return; + if (!bus.writeRead({}, rx)) { + reportFailure("command acknowledged but no measurement came back"); + return; + } auto reading = decodeSht41Measurement(rx); if (!reading) { @@ -48,6 +75,11 @@ struct Sht41EnvSensor::Impl { return; } + if (fail_streak > 0) { + LOG_INFO << "SHT41: responding again after " << fail_streak << " failed read(s)"; + fail_streak = 0; + } + std::lock_guard lock(mutex); latest.valid = true; latest.temp_c = reading->temp_c; @@ -81,7 +113,11 @@ void Sht41EnvSensor::start() { std::this_thread::sleep_for(kSoftResetSettle); impl_->running = true; impl_->worker = std::thread([this] { impl_->run(); }); - LOG_INFO << "SHT41 env sensor started"; + // Deliberately not "started OK": opening the node proves nothing about the + // sensor being there. The first read either succeeds quietly or logs why not. + LOG_INFO << "SHT41 env sensor polling 0x" << std::hex << static_cast(impl_->bus.address()) + << std::dec << " on " << impl_->bus.device() << " every " + << impl_->period.count() << " ms"; } void Sht41EnvSensor::stop() {