From 3c6160440328d90dd70cc689da6255c860f3ca3b Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Wed, 10 Apr 2024 20:23:20 +0300 Subject: [PATCH] sns: pre-init and recover dallas ability to init later --- code/espurna/sensor.cpp | 131 ++++------------ code/espurna/sensor.h | 17 ++ code/espurna/sensors/DallasSensor.h | 230 ++++++++++++++++++++++++++++ 3 files changed, 278 insertions(+), 100 deletions(-) diff --git a/code/espurna/sensor.cpp b/code/espurna/sensor.cpp index 41447796dd..6f9661fd20 100644 --- a/code/espurna/sensor.cpp +++ b/code/espurna/sensor.cpp @@ -1944,6 +1944,8 @@ std::vector sensors; size_t report_every { build::reportEvery() }; duration::Seconds read_interval { build::readInterval() }; + +std::forward_list pre_init; duration::Seconds init_interval { build::initInterval() }; } // namespace internal @@ -2088,103 +2090,7 @@ void load() { #if DALLAS_SUPPORT { - PROGMEM_STRING(Pin, "dallasPin"); - const auto pin = getSetting(Pin, uint8_t{ DALLAS_PIN }); - - PROGMEM_STRING(Parasite, "dallasParasite"); - const auto parasite = getSetting(Parasite, DALLAS_PARASITE == 1); - - PROGMEM_STRING(Resolution, "dallasResolution"); - const auto resolution = getSetting(Resolution, uint8_t{ DALLAS_RESOLUTION }); - - using namespace espurna::driver; - auto port = std::make_shared(); - - // TODO hybrid mode with an extra pull-up pin? - // TODO parasite *can* be detected for DS18X, see - // 'DS18B20 .pdf / ROM Commands / Read Power Supply (0xB4)' - // > During the read time slot, parasite powered DS18B20s will - // > pull the bus low, and externally powered DS18B20s will - // > let the bus remain high. - // (but, not every DS clone properly implements it) - auto error = port->attach(pin, parasite); - if (onewire::Error::Ok == error) { - using namespace espurna::sensor::driver; - - const auto devices = port->devices(); - DEBUG_MSG_P(PSTR("[DALLAS] Found %zu device(s) on GPIO%hhu\n"), - devices.size(), pin); - - std::vector filtered; - filtered.reserve(devices.size()); - - for (auto& device : devices) { - filtered.push_back(&device); - } - - // Currently known sensor types - using Temperature = dallas::temperature::Sensor; - using Digital = dallas::temperature::Sensor; - - const auto unknown = std::remove_if( - filtered.begin(), filtered.end(), - [](const onewire::Device* device) { - if (!Temperature::match(*device) - && !Digital::match(*device)) - { - DEBUG_MSG_P(PSTR("[DALLAS] Unknown device %s\n"), - hexEncode(device->address).c_str()); - return true; - } - - return false; - }); - filtered.erase(unknown, filtered.end()); - - if (!filtered.size()) { - error = onewire::Error::NotFound; - goto dallas_end; - } - - // Push digital sensors first, temperature sensors last - // Making sure temperature sensor always becomes port handler - std::sort( - filtered.begin(), filtered.end(), - [](const onewire::Device* lhs, const onewire::Device*) { - return Digital::match(*lhs); - }); - - // TODO per-sensor resolution matters much? - dallas::temperature::Sensor::setResolution(resolution); - - dallas::internal::Sensor* ptr = nullptr; - for (const auto* device : filtered) { - if (Temperature::match(*device)) { - ptr = new Temperature(port, *device); - } else if (Digital::match(*device)) { - ptr = new Digital(port, *device); - } else { - error = onewire::Error::NotFound; - goto dallas_end; - } - - sensor::add(ptr); - } - - // Since sensor reading order is constant, make sure the - // last sensor is handling everything related to the wire. - // (also note 'Digital' being pushed to the front above) - DEBUG_MSG_P(PSTR("[DALLAS] %s is port handler\n"), - hexEncode(ptr->getDeviceAddress()).c_str()); - ptr->setPortHandler(); - } - -dallas_end: - if (onewire::Error::Ok != error) { - DEBUG_MSG_P(PSTR("[DALLAS] Could not initialize the sensor - %.*s\n"), - espurna::driver::onewire::error(error).length(), - espurna::driver::onewire::error(error).data()); - } + sensor::driver::dallas::load(); } #endif @@ -4003,6 +3909,25 @@ void resume() { bool init() { bool out { true }; + while (!internal::pre_init.empty()) { + auto it = internal::pre_init.begin(); + + auto result = (*it)->find_sensors(); + if (result.error != SENSOR_ERROR_OK) { + DEBUG_MSG_P(PSTR("[SENSOR] %s -> ERROR %s (%hhu)\n"), + (*it)->description().c_str(), + sensor::error(result.error).c_str(), result.error); + out = false; + break; + } + + for (BaseSensor& ptr : result.sensors) { + sensor::add(&ptr); + } + + internal::pre_init.pop_front(); + } + for (auto sensor : internal::sensors) { // Do not process an already initialized sensor if (sensor->ready()) { @@ -4010,14 +3935,13 @@ bool init() { } // Force sensor to reload config - DEBUG_MSG_P(PSTR("[SENSOR] Initializing %s\n"), - sensor->description().c_str()); sensor->begin(); if (!sensor->ready()) { const auto error = sensor->error(); if (error != SENSOR_ERROR_OK) { - DEBUG_MSG_P(PSTR("[SENSOR] -> ERROR %s (%hhu)\n"), + DEBUG_MSG_P(PSTR("[SENSOR] %s -> ERROR %s (%hhu)\n"), + sensor->description().c_str(), sensor::error(error).c_str(), error); } out = false; @@ -4362,6 +4286,13 @@ void setup() { } } // namespace + +PreInit::~PreInit() = default; + +void add_preinit(PreInitPtr ptr) { + internal::pre_init.push_front(std::move(ptr)); +} + } // namespace sensor } // namespace espurna diff --git a/code/espurna/sensor.h b/code/espurna/sensor.h index e9946e06f8..b53de8b43e 100644 --- a/code/espurna/sensor.h +++ b/code/espurna/sensor.h @@ -245,6 +245,23 @@ using NotifyCallback = bool (*)(const BaseSensor*); void notify_after(duration::Milliseconds, NotifyCallback); void notify_now(NotifyCallback); +struct PreInit { + using Sensors = Span; + + struct Result { + Sensors sensors; + int error; + }; + + virtual ~PreInit(); + + virtual Result find_sensors() = 0; + virtual String description() const = 0; +}; + +using PreInitPtr = std::unique_ptr; +void add_preinit(PreInitPtr); + } // namespace sensor } // namespace espurna diff --git a/code/espurna/sensors/DallasSensor.h b/code/espurna/sensors/DallasSensor.h index abae88631e..677a4035ea 100644 --- a/code/espurna/sensors/DallasSensor.h +++ b/code/espurna/sensors/DallasSensor.h @@ -702,6 +702,236 @@ class Sensor : public internal::Sensor { } // namespace digital +namespace build { + +constexpr uint8_t pin() { + return DALLAS_PIN; +} + +constexpr bool parasite() { + return 1 == DALLAS_PARASITE; +} + +constexpr uint8_t resolution() { + return DALLAS_RESOLUTION; +} + +} // namespace build + +namespace settings { +namespace keys { + +PROGMEM_STRING(Pin, "dallasPin"); +PROGMEM_STRING(Parasite, "dallasParasite"); +PROGMEM_STRING(Resolution, "dallasResolution"); + +} // namespace keys + +uint8_t pin() { + return getSetting(keys::Pin, build::pin()); +} + +bool parasite() { + return getSetting(keys::Parasite, build::parasite()); +} + +uint8_t resolution() { + return getSetting(keys::Resolution, build::resolution()); +} + +} // namespace settings + +struct Config { + uint8_t pin; + bool parasite; + uint8_t resolution; +}; + +Config make_config() { + return Config{ + .pin = settings::pin(), + .parasite = settings::parasite(), + .resolution = settings::resolution(), + }; +} + +class Init : public sensor::PreInit { +public: + explicit Init(Config config) : + _config(config) + {} + + Result find_sensors() override { + return _find_sensors(); + } + + String description() const override { + return STRING_VIEW("DallasSensor").toString(); + } + +private: + using Device = espurna::driver::onewire::Device; + + using Port = espurna::driver::onewire::Port; + using PortPtr = std::shared_ptr; + + using OneWireError = espurna::driver::onewire::Error; + + Result _find_sensors() { + const int err = _sensors.size() + ? SENSOR_ERROR_OK + : _find(); + + return Result{ + .sensors = Sensors( + *_sensors.data(), + _sensors.size()), + .error = err, + }; + } + + int _find() { + if (_sensors.size()) { + return SENSOR_ERROR_OK; + } + + if (!_port) { + _port = std::make_shared(); + } + + // TODO hybrid mode with an extra pull-up pin? + // TODO parasite *can* be detected for DS18X, see + // 'DS18B20 .pdf / ROM Commands / Read Power Supply (0xB4)' + // > During the read time slot, parasite powered DS18B20s will + // > pull the bus low, and externally powered DS18B20s will + // > let the bus remain high. + // (but, not every DS clone properly implements it) + auto error = _port->attach(_config.pin, _config.parasite); + + using namespace espurna::driver; + if (OneWireError::Ok != error) { + return _translate(error); + } + + const auto filtered = _filter(_port->devices()); + if (!filtered.size()) { + return SENSOR_ERROR_NOT_FOUND; + } + + _populate(_port, filtered); + + return SENSOR_ERROR_OK; + } + + int _translate(OneWireError error) { + using namespace espurna::driver; + int out; + + switch (error) { + case OneWireError::GpioUsed: + out = SENSOR_ERROR_GPIO_USED; + break; + case OneWireError::NotFound: + out = SENSOR_ERROR_NOT_FOUND; + break; + case OneWireError::Unresponsive: + out = SENSOR_ERROR_NOT_READY; + break; + case OneWireError::Config: + out = SENSOR_ERROR_CONFIG; + break; + case OneWireError::Ok: + out = SENSOR_ERROR_OK; + break; + } + + return out; + } + + std::vector _filter(Span devices) { + using namespace espurna::driver; + + std::vector filtered; + filtered.reserve(devices.size()); + + for (auto& device : devices) { + filtered.push_back(&device); + } + + const auto unknown = std::remove_if( + filtered.begin(), filtered.end(), + [](const onewire::Device* device) { + if (!temperature::Sensor::match(*device) + && !digital::Sensor::match(*device)) + { + DEBUG_MSG_P(PSTR("[DALLAS] Unknown device %s\n"), + hexEncode(device->address).c_str()); + return true; + } + + return false; + }); + + filtered.erase(unknown, filtered.end()); + + if (filtered.size()) { + // Push digital sensors first, temperature sensors last + // Making sure temperature sensor always becomes port handler + std::sort( + filtered.begin(), filtered.end(), + [](const onewire::Device* lhs, const onewire::Device*) { + return digital::Sensor::match(*lhs); + }); + } + + return filtered; + } + + void _populate(PortPtr port, std::vector devices) { + if (_sensors.size()) { + return; + } + + // TODO per-sensor resolution matters much? + temperature::Sensor::setResolution(_config.resolution); + + using Temperature = dallas::temperature::Sensor; + using Digital = dallas::temperature::Sensor; + + internal::Sensor* ptr = nullptr; + _sensors.reserve(devices.size()); + + for (auto& device : devices) { + if (Temperature::match(*device)) { + ptr = new Temperature(port, *device); + } else if (Digital::match(*device)) { + ptr = new Digital(port, *device); + } else { + break; + } + + _sensors.push_back(ptr); + } + + // Since sensor reading order is constant, make sure the + // last sensor is handling everything related to the wire. + // (also note 'Digital' being pushed to the front above) + DEBUG_MSG_P(PSTR("[DALLAS] %s is port handler\n"), + hexEncode(ptr->getDeviceAddress()).c_str()); + ptr->setPortHandler(); + } + + Config _config; + + PortPtr _port; + std::vector _sensors; +}; + +inline void load() { + sensor::add_preinit( + std::make_unique(make_config())); +} + } // namespace } // namespace dallas } // namespace driver