Browse Source

sns: delayed initialization fixes

- configure magnitudes right after they are created.
  if any sensor fails begin(), this would leave .filter uninitialized
- poll on flags instead of raw timestamps.
  minor change to get rid of the init state.
- system::timer::SystemTimerFlag -> system::ReadyFlag
test/dev
Maxim Prokhorov 2 months ago
parent
commit
4cc0936a9c
3 changed files with 175 additions and 116 deletions
  1. +133
    -116
      code/espurna/sensor.cpp
  2. +20
    -0
      code/espurna/system.cpp
  3. +22
    -0
      code/espurna/system.h

+ 133
- 116
code/espurna/sensor.cpp View File

@ -1751,8 +1751,9 @@ size_t count() {
return internal::magnitudes.size();
}
void add(BaseSensorPtr sensor, unsigned char slot, unsigned char type) {
Magnitude& add(BaseSensorPtr sensor, unsigned char slot, unsigned char type) {
internal::magnitudes.emplace_back(sensor, slot, type);
return internal::magnitudes.back();
}
const Magnitude* find(unsigned char type, unsigned char index) {
@ -3880,13 +3881,105 @@ void setup() {
namespace internal {
State state;
State state { State::None };
std::unique_ptr<ReadyFlag> init_flag;
TimeSource::time_point last_init;
TimeSource::time_point last_reading;
ReadyFlag read_flag;
} // namespace internal
void configure_magnitude(Magnitude& magnitude) {
// TODO: namespace and various helpers need some naming tweaks...
// Only initialized once, notify about reset requirement?
if (!magnitude.filter) {
magnitude.filter_type = getSetting(
settings::keys::get(magnitude, settings::suffix::Filter),
magnitude::defaultFilter(magnitude));
magnitude.filter = magnitude::makeFilter(magnitude.filter_type);
}
// Some filters must be able store up to a certain amount of readings.
magnitude.filter->resize(reportEvery());
// process emon-specific settings first. ensure that settings use global index and we access sensor with the local one
if (isEmon(magnitude.sensor) && magnitude::traits::ratio_supported(magnitude.type)) {
auto* sensor = static_cast<BaseEmonSensor*>(magnitude.sensor.get());
sensor->setRatio(magnitude.slot, getSetting(
settings::keys::get(magnitude, settings::suffix::Ratio),
sensor->defaultRatio(magnitude.slot)));
}
// analog variant of emon sensor has some additional settings
if (isAnalogEmon(magnitude.sensor) && (magnitude.type == MAGNITUDE_VOLTAGE)) {
auto* sensor = static_cast<BaseAnalogEmonSensor*>(magnitude.sensor.get());
sensor->setVoltage(getSetting(
settings::keys::get(magnitude, settings::suffix::Mains),
sensor->defaultVoltage()));
sensor->setReferenceVoltage(getSetting(
settings::keys::get(magnitude, settings::suffix::Reference),
sensor->defaultReferenceVoltage()));
}
// adjust units based on magnitude's type
magnitude.units = units::filter(magnitude,
getSetting(
settings::keys::get(magnitude, settings::suffix::Units),
magnitude.sensor->units(magnitude.slot)));
// adjust resulting value (simple plus or minus)
// TODO: inject math or rpnlib expression?
if (magnitude::traits::correction_supported(magnitude.type)) {
magnitude.correction = getSetting(
settings::keys::get(magnitude, settings::suffix::Correction),
magnitude::build::correction(magnitude.type));
}
// pick decimal precision either from our (sane) defaults of from the sensor itself
// (specifically, when sensor has more or less precision than we expect)
{
const auto decimals = magnitude.sensor->decimals(magnitude.units);
magnitude.decimals = getSetting(
settings::keys::get(magnitude, settings::suffix::Precision),
(decimals >= 0)
? static_cast<unsigned char>(decimals)
: magnitude::decimals(magnitude.units));
}
// Per-magnitude min & max delta settings for reporting the value
// - ${prefix}MinDelta${index} controls whether we report when report counter overflows
// (default is set to 0.0 aka value has changed from the last recorded one)
// - ${prefix}MaxDelta${index} will trigger report as soon as read value is greater than the specified delta
// (default is 0.0 as well, but this needs to be >0 to actually do something)
magnitude.min_delta = getSetting(
settings::keys::get(magnitude, settings::suffix::MinDelta),
build::DefaultMinDelta);
magnitude.max_delta = getSetting(
settings::keys::get(magnitude, settings::suffix::MaxDelta),
build::DefaultMaxDelta);
// Sometimes we want to ensure the value is above certain threshold before reporting
magnitude.zero_threshold = getSetting(
settings::keys::get(magnitude, settings::suffix::ZeroThreshold),
Value::Unknown);
// When we don't save energy, purge existing value in both RAM & settings
if (isEmon(magnitude.sensor) && (MAGNITUDE_ENERGY == magnitude.type) && (0 == energy::every())) {
energy::reset(magnitude.index_global);
}
}
// Update magnitude config, filter sizes and reset energy if needed
void configure_magnitudes() {
for (auto& magnitude : magnitude::internal::magnitudes) {
configure_magnitude(magnitude);
}
}
void schedule_read() {
internal::read_flag.wait(internal::read_interval);
}
void suspend() {
for (auto& sensor : internal::sensors) {
sensor->suspend();
@ -3894,8 +3987,7 @@ void suspend() {
}
void resume() {
internal::last_init = TimeSource::now();
internal::last_reading = TimeSource::now();
schedule_read();
magnitude::forEachInstance(
[](sensor::Magnitude& instance) {
@ -3934,7 +4026,8 @@ bool init() {
const auto slots = sensor->count();
for (auto slot = 0; slot < slots; ++slot) {
magnitude::add(sensor, slot, sensor->type(slot));
auto& result = magnitude::add(sensor, slot, sensor->type(slot));
configure_magnitude(result);
}
}
@ -3960,11 +4053,25 @@ bool init() {
return out;
}
bool try_init() {
const auto timestamp = TimeSource::now();
if (timestamp - internal::last_init > initInterval()) {
internal::last_init = timestamp;
return init();
// setup() helper. try init() and schedule re-initialization in loop()
void try_init() {
if (!init()) {
using T = decltype(internal::init_flag)::element_type;
auto flag = std::make_unique<T>();
flag->wait(internal::init_interval);
internal::init_flag = std::move(flag);
}
}
// loop() helper. if init flag was set previously, re-try init() until it works
bool maybe_try_init(duration::Milliseconds interval) {
if (!internal::init_flag) {
return true;
}
if (internal::init_flag->wait(interval) && init()) {
internal::init_flag.reset(nullptr);
return true;
}
return false;
@ -4022,24 +4129,14 @@ void error() {
#endif
}
void reset_init(duration::Seconds init_interval) {
internal::init_interval = init_interval;
}
void reset_report(duration::Seconds read_interval, size_t report_every) {
internal::read_interval = read_interval;
internal::report_every = report_every;
internal::last_reading = TimeSource::now();
internal::read_flag.stop_wait(read_interval);
}
bool ready_to_read() {
const auto timestamp = TimeSource::now();
if (timestamp - internal::last_reading > readInterval()) {
internal::last_reading = timestamp;
return true;
}
return false;
return internal::read_flag.wait(internal::read_interval);
}
void loop() {
@ -4055,7 +4152,7 @@ void loop() {
// General initialization, generate magnitudes from available sensors
if (internal::state == State::Initial) {
if (try_init()) {
if (maybe_try_init(settings::initInterval())) {
internal::state = State::Ready;
}
}
@ -4202,115 +4299,35 @@ void loop() {
}
}
void configure() {
// Read interval is shared between every sensor
// TODO: implement scheduling in the sensor itself.
// allow reads faster than 1sec, not just internal ones via tick()
// allow 'manual' sensors that may be triggered programatically
void configure_base() {
// Read counter is set for each magnitude, and equals to 0 right after this point
reset_report(
sensor::settings::readInterval(),
sensor::settings::reportEvery());
// Initialization interval is also shared
reset_init(sensor::settings::initInterval());
// Generic 'get magnitude value' API calls prefer latest values over the reported ones
magnitude::prefer_real_time_values(sensor::settings::realTimeValues());
// TODO: something more generic? energy is an accumulating value, only allow for similar ones?
// TODO: move to an external module?
energy::every(sensor::settings::saveEvery());
}
// Update magnitude config, filter sizes and reset energy if needed
// TODO: namespace and various helpers need some naming tweaks...
for (auto& magnitude : magnitude::internal::magnitudes) {
// Only initialized once, notify about reset requirement?
if (!magnitude.filter) {
magnitude.filter_type = getSetting(
settings::keys::get(magnitude, settings::suffix::Filter),
magnitude::defaultFilter(magnitude));
magnitude.filter = magnitude::makeFilter(magnitude.filter_type);
}
// Some filters must be able store up to a certain amount of readings.
magnitude.filter->resize(reportEvery());
// process emon-specific settings first. ensure that settings use global index and we access sensor with the local one
if (isEmon(magnitude.sensor) && magnitude::traits::ratio_supported(magnitude.type)) {
auto* sensor = static_cast<BaseEmonSensor*>(magnitude.sensor.get());
sensor->setRatio(magnitude.slot, getSetting(
settings::keys::get(magnitude, settings::suffix::Ratio),
sensor->defaultRatio(magnitude.slot)));
}
// analog variant of emon sensor has some additional settings
if (isAnalogEmon(magnitude.sensor) && (magnitude.type == MAGNITUDE_VOLTAGE)) {
auto* sensor = static_cast<BaseAnalogEmonSensor*>(magnitude.sensor.get());
sensor->setVoltage(getSetting(
settings::keys::get(magnitude, settings::suffix::Mains),
sensor->defaultVoltage()));
sensor->setReferenceVoltage(getSetting(
settings::keys::get(magnitude, settings::suffix::Reference),
sensor->defaultReferenceVoltage()));
}
// adjust units based on magnitude's type
magnitude.units = units::filter(magnitude,
getSetting(
settings::keys::get(magnitude, settings::suffix::Units),
magnitude.sensor->units(magnitude.slot)));
// adjust resulting value (simple plus or minus)
// TODO: inject math or rpnlib expression?
if (magnitude::traits::correction_supported(magnitude.type)) {
magnitude.correction = getSetting(
settings::keys::get(magnitude, settings::suffix::Correction),
magnitude::build::correction(magnitude.type));
}
// pick decimal precision either from our (sane) defaults of from the sensor itself
// (specifically, when sensor has more or less precision than we expect)
{
const auto decimals = magnitude.sensor->decimals(magnitude.units);
magnitude.decimals = getSetting(
settings::keys::get(magnitude, settings::suffix::Precision),
(decimals >= 0)
? static_cast<unsigned char>(decimals)
: magnitude::decimals(magnitude.units));
}
// Per-magnitude min & max delta settings for reporting the value
// - ${prefix}MinDelta${index} controls whether we report when report counter overflows
// (default is set to 0.0 aka value has changed from the last recorded one)
// - ${prefix}MaxDelta${index} will trigger report as soon as read value is greater than the specified delta
// (default is 0.0 as well, but this needs to be >0 to actually do something)
magnitude.min_delta = getSetting(
settings::keys::get(magnitude, settings::suffix::MinDelta),
build::DefaultMinDelta);
magnitude.max_delta = getSetting(
settings::keys::get(magnitude, settings::suffix::MaxDelta),
build::DefaultMaxDelta);
// Sometimes we want to ensure the value is above certain threshold before reporting
magnitude.zero_threshold = getSetting(
settings::keys::get(magnitude, settings::suffix::ZeroThreshold),
Value::Unknown);
// When we don't save energy, purge existing value in both RAM & settings
if (isEmon(magnitude.sensor) && (MAGNITUDE_ENERGY == magnitude.type) && (0 == energy::every())) {
energy::reset(magnitude.index_global);
}
}
void configure() {
configure_base();
configure_magnitudes();
}
void setup() {
// Make sure settings stay up-to-date
migrateVersion(settings::migrate);
// Load & initialize magnitudes from available sensors
sensor::load();
sensor::init();
sensor::try_init();
// Configure based on settings
sensor::configure();
// Minimal configuration required before entering loop()
sensor::configure_base();
// Allow us to query key default
sensor::settings::query::setup();


+ 20
- 0
code/espurna/system.cpp View File

@ -734,6 +734,26 @@ void SystemTimer::schedule_once(Duration duration, Callback callback) {
} // namespace timer
bool ReadyFlag::wait(duration::Milliseconds interval) {
if (_ready) {
_ready = false;
_timer.schedule_once(
interval,
[&]() {
_ready = true;
});
return true;
}
return false;
}
void ReadyFlag::stop() {
_timer.stop();
_ready = true;
}
namespace {
namespace memory {


+ 22
- 0
code/espurna/system.h View File

@ -282,6 +282,28 @@ private:
} // namespace timer
struct ReadyFlag {
bool wait(duration::Milliseconds);
void stop();
void stop_wait(duration::Milliseconds duration) {
stop();
wait(duration);
}
bool ready() const {
return _ready;
}
explicit operator bool() const {
return ready();
}
private:
bool _ready { true };
timer::SystemTimer _timer;
};
namespace heartbeat {
using Mask = int32_t;


Loading…
Cancel
Save