mirror of
https://github.com/letscontrolit/ESPEasy.git
synced 2026-09-12 09:36:59 +00:00
Merge pull request #4320 from TD-er/feature/GPS_custom_stats_view
[GPS] Use custom GPS view of plugin stats data + add std.dev
This commit is contained in:
@@ -93,6 +93,8 @@ For example using just like normal task value data:
|
||||
|
||||
* ``[bme#temp.avg]`` Compute the average over the last N samples in the historic buffer (typically: 64 samples on ESP32, 16 on ESP8266)
|
||||
* ``[bme#temp.avgX]`` Compute the average over the last X samples (or less if there are less samples available)
|
||||
* ``[bme#temp.stddev]`` Compute the standard deviation over the last N samples in the historic buffer (typically: 64 samples on ESP32, 16 on ESP8266)
|
||||
* ``[bme#temp.stddevX]`` Compute the standard deviation over the last X samples (or less if there are less samples available)
|
||||
* ``[bme#temp.max]`` Refer to the maximum recorded sample since the last ``resetpeaks``. N.B. Not all tasks log the min and max peaks.
|
||||
* ``[bme#temp.min]`` See ``[bme#temp.max]``
|
||||
|
||||
|
||||
@@ -315,6 +315,24 @@ boolean Plugin_082(uint8_t function, struct EventStruct *event, String& string)
|
||||
break;
|
||||
}
|
||||
|
||||
# if FEATURE_PLUGIN_STATS
|
||||
case PLUGIN_WEBFORM_LOAD_SHOW_STATS:
|
||||
{
|
||||
P082_data_struct *P082_data =
|
||||
static_cast<P082_data_struct *>(getPluginTaskData(event->TaskIndex));
|
||||
|
||||
if (nullptr != P082_data) {
|
||||
for (uint8_t i = 0; i < P082_NR_OUTPUT_VALUES; ++i) {
|
||||
const uint8_t pconfigIndex = i + P082_QUERY1_CONFIG_POS;
|
||||
if (P082_data->webformLoad_show_stats(event, i, static_cast<P082_query>(PCONFIG(pconfigIndex)))) {
|
||||
success = true; // Something added
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
# endif // if FEATURE_PLUGIN_STATS
|
||||
|
||||
case PLUGIN_INIT: {
|
||||
if (P082_TIMEOUT < 100) {
|
||||
P082_TIMEOUT = P082_DEFAULT_FIX_TIMEOUT;
|
||||
|
||||
@@ -12,6 +12,7 @@ PluginStats::PluginStats(uint8_t nrDecimals, float errorValue) :
|
||||
_nrDecimals(nrDecimals)
|
||||
|
||||
{
|
||||
_errorValueIsNaN = isnan(_errorValue);
|
||||
resetPeaks();
|
||||
}
|
||||
|
||||
@@ -45,14 +46,10 @@ float PluginStats::getSampleAvg(PluginStatsBuffer_t::index_t lastNrSamples) cons
|
||||
}
|
||||
PluginStatsBuffer_t::index_t samplesUsed = 0;
|
||||
|
||||
const bool errorValueIsNaN = isnan(_errorValue);
|
||||
|
||||
for (; i < _samples.size(); ++i) {
|
||||
if (!isnan(_samples[i])) {
|
||||
if (errorValueIsNaN || !essentiallyEqual(_errorValue, _samples[i])) {
|
||||
++samplesUsed;
|
||||
sum += _samples[i];
|
||||
}
|
||||
if (usableValue(_samples[i])) {
|
||||
++samplesUsed;
|
||||
sum += _samples[i];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +57,32 @@ float PluginStats::getSampleAvg(PluginStatsBuffer_t::index_t lastNrSamples) cons
|
||||
return sum / samplesUsed;
|
||||
}
|
||||
|
||||
float PluginStats::getSampleStdDev(PluginStatsBuffer_t::index_t lastNrSamples) const
|
||||
{
|
||||
float variance = 0.0f;
|
||||
const float average = getSampleAvg(lastNrSamples);
|
||||
if (!usableValue(average)) { return 0.0f; }
|
||||
|
||||
PluginStatsBuffer_t::index_t i = 0;
|
||||
|
||||
if (lastNrSamples < _samples.size()) {
|
||||
i = _samples.size() - lastNrSamples;
|
||||
}
|
||||
PluginStatsBuffer_t::index_t samplesUsed = 0;
|
||||
|
||||
for (; i < _samples.size(); ++i) {
|
||||
if (usableValue(_samples[i])) {
|
||||
++samplesUsed;
|
||||
const float diff = _samples[i] - average;
|
||||
variance += diff * diff;
|
||||
}
|
||||
}
|
||||
if (samplesUsed < 2) { return 0.0f; }
|
||||
|
||||
variance /= samplesUsed;
|
||||
return sqrtf(variance);
|
||||
}
|
||||
|
||||
float PluginStats::operator[](PluginStatsBuffer_t::index_t index) const
|
||||
{
|
||||
if (index < _samples.size()) { return _samples[index]; }
|
||||
@@ -98,6 +121,22 @@ bool PluginStats::plugin_get_config_value_base(struct EventStruct *event, String
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (command.startsWith(F("stddev"))) {
|
||||
if (command.equals(F("stddev"))) { // [taskname#valuename.stddev] Std deviation of the last N kept samples
|
||||
value = getSampleStdDev();
|
||||
success = true;
|
||||
} else {
|
||||
// Check for "stddevN", where N is the number of most recent samples to use.
|
||||
int nrSamples = 0;
|
||||
|
||||
if (validIntFromString(command.substring(3), nrSamples)) {
|
||||
if (nrSamples > 0) {
|
||||
// [taskname#valuename.stddevN] Std. deviation over N most recent samples
|
||||
value = getSampleStdDev(nrSamples);
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (success) {
|
||||
@@ -112,6 +151,8 @@ bool PluginStats::webformLoad_show_stats(struct EventStruct *event) const
|
||||
|
||||
if (webformLoad_show_avg(event)) { somethingAdded = true; }
|
||||
|
||||
if (webformLoad_show_stdev(event)) { somethingAdded = true; }
|
||||
|
||||
if (webformLoad_show_peaks(event)) { somethingAdded = true; }
|
||||
|
||||
if (somethingAdded) {
|
||||
@@ -134,18 +175,51 @@ bool PluginStats::webformLoad_show_avg(struct EventStruct *event) const
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PluginStats::webformLoad_show_peaks(struct EventStruct *event) const
|
||||
bool PluginStats::webformLoad_show_stdev(struct EventStruct *event) const
|
||||
{
|
||||
if (hasPeaks()) {
|
||||
const float stdDev = getSampleStdDev();
|
||||
if (usableValue(stdDev) && getNrSamples() > 1) {
|
||||
addRowLabel(getLabel() + F(" std. dev"));
|
||||
addHtmlFloat(stdDev, _nrDecimals);
|
||||
addHtml(' ', '(');
|
||||
addHtmlInt(getNrSamples());
|
||||
addHtml(F(" samples)"));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PluginStats::webformLoad_show_peaks(struct EventStruct *event, bool include_peak_to_peak) const
|
||||
{
|
||||
if (hasPeaks() && getNrSamples() > 1) {
|
||||
addRowLabel(getLabel() + F(" Peak Low/High"));
|
||||
addHtmlFloat(getPeakLow(), _nrDecimals);
|
||||
addHtml('/');
|
||||
addHtmlFloat(getPeakHigh(), _nrDecimals);
|
||||
|
||||
if (include_peak_to_peak) {
|
||||
addRowLabel(getLabel() + F(" Peak-to-peak"));
|
||||
addHtmlFloat(getPeakHigh() - getPeakLow(), _nrDecimals);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PluginStats::webformLoad_show_val(
|
||||
struct EventStruct *event,
|
||||
const String & label,
|
||||
double value,
|
||||
const String & unit) const
|
||||
{
|
||||
addRowLabel(getLabel() + label);
|
||||
addHtmlFloat(value, _nrDecimals);
|
||||
|
||||
if (!unit.isEmpty()) {
|
||||
addUnit(unit);
|
||||
}
|
||||
}
|
||||
|
||||
# if FEATURE_CHART_JS
|
||||
void PluginStats::plot_ChartJS_dataset() const
|
||||
{
|
||||
@@ -170,6 +244,16 @@ void PluginStats::plot_ChartJS_dataset() const
|
||||
|
||||
# endif // if FEATURE_CHART_JS
|
||||
|
||||
bool PluginStats::usableValue(float value) const
|
||||
{
|
||||
if (!isnan(value)) {
|
||||
if (_errorValueIsNaN || !essentiallyEqual(_errorValue, value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
PluginStats_array::PluginStats_array()
|
||||
{
|
||||
for (size_t i = 0; i < VARS_PER_TASK; ++i) {
|
||||
@@ -312,7 +396,9 @@ bool PluginStats_array::webformLoad_show_stats(struct EventStruct *event) const
|
||||
|
||||
for (size_t i = 0; i < VARS_PER_TASK; ++i) {
|
||||
if (_plugin_stats[i] != nullptr) {
|
||||
if (_plugin_stats[i]->webformLoad_show_stats(event)) { somethingAdded = true; }
|
||||
if (_plugin_stats[i]->webformLoad_show_stats(event)) {
|
||||
somethingAdded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return somethingAdded;
|
||||
|
||||
@@ -70,18 +70,34 @@ public:
|
||||
// Compute average over last N stored values
|
||||
float getSampleAvg(PluginStatsBuffer_t::index_t lastNrSamples) const;
|
||||
|
||||
// Compute the standard deviation over all stored values
|
||||
float getSampleStdDev() const {
|
||||
return getSampleStdDev(_samples.size());
|
||||
}
|
||||
|
||||
// Compute the standard deviation over last N stored values
|
||||
float getSampleStdDev(PluginStatsBuffer_t::index_t lastNrSamples) const;
|
||||
|
||||
|
||||
float operator[](PluginStatsBuffer_t::index_t index) const;
|
||||
|
||||
|
||||
// Support task value notation to 'get' statistics
|
||||
// Notations like [taskname#taskvalue.avg] can then be used to compute the average over a number of samples.
|
||||
bool plugin_get_config_value_base(struct EventStruct *event,
|
||||
String & string) const;
|
||||
bool plugin_get_config_value_base(struct EventStruct *event,
|
||||
String & string) const;
|
||||
|
||||
bool webformLoad_show_stats(struct EventStruct *event) const;
|
||||
bool webformLoad_show_stats(struct EventStruct *event) const;
|
||||
|
||||
bool webformLoad_show_avg(struct EventStruct *event) const;
|
||||
bool webformLoad_show_peaks(struct EventStruct *event) const;
|
||||
bool webformLoad_show_avg(struct EventStruct *event) const;
|
||||
bool webformLoad_show_stdev(struct EventStruct *event) const;
|
||||
bool webformLoad_show_peaks(struct EventStruct *event,
|
||||
bool include_peak_to_peak = true) const;
|
||||
void webformLoad_show_val(
|
||||
struct EventStruct *event,
|
||||
const String & label,
|
||||
double value,
|
||||
const String & unit) const;
|
||||
|
||||
|
||||
const String& getLabel() const {
|
||||
@@ -121,11 +137,14 @@ public:
|
||||
|
||||
private:
|
||||
|
||||
bool usableValue(float value) const;
|
||||
|
||||
float _minValue;
|
||||
float _maxValue;
|
||||
|
||||
PluginStatsBuffer_t _samples;
|
||||
float _errorValue;
|
||||
bool _errorValueIsNaN;
|
||||
|
||||
uint8_t _nrDecimals = 3u;
|
||||
};
|
||||
|
||||
@@ -287,12 +287,16 @@ bool P002_data_struct::webformLoad_show_stats(struct EventStruct *event)
|
||||
{
|
||||
bool somethingAdded = false;
|
||||
|
||||
if (getPluginStats(0) != nullptr) {
|
||||
if (getPluginStats(0)->webformLoad_show_avg(event)) { somethingAdded = true; }
|
||||
const PluginStats* stats = getPluginStats(0);
|
||||
|
||||
if (getPluginStats(0)->hasPeaks()) {
|
||||
formatADC_statistics(F("ADC Peak Low"), getPluginStats(0)->getPeakLow(), true);
|
||||
formatADC_statistics(F("ADC Peak High"), getPluginStats(0)->getPeakHigh(), true);
|
||||
if (stats != nullptr) {
|
||||
if (stats->webformLoad_show_avg(event)) { somethingAdded = true; }
|
||||
|
||||
if (stats->webformLoad_show_stdev(event)) { somethingAdded = true; }
|
||||
|
||||
if (stats->hasPeaks()) {
|
||||
formatADC_statistics(F("ADC Peak Low"), stats->getPeakLow(), true);
|
||||
formatADC_statistics(F("ADC Peak High"), stats->getPeakHigh(), true);
|
||||
somethingAdded = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#ifdef USES_P082
|
||||
|
||||
|
||||
// Needed also here for PlatformIO's library finder as the .h file
|
||||
// Needed also here for PlatformIO's library finder as the .h file
|
||||
// is in a directory which is excluded in the src_filter
|
||||
# include <TinyGPS++.h>
|
||||
# include <ESPeasySerial.h>
|
||||
@@ -161,7 +161,7 @@ bool P082_data_struct::loop() {
|
||||
done = true;
|
||||
break;
|
||||
default:
|
||||
done = true;
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -221,8 +221,8 @@ bool P082_data_struct::storeCurPos(unsigned int maxAge_msec) {
|
||||
}
|
||||
|
||||
_distance += distanceSinceLast(maxAge_msec);
|
||||
_last_lat = gps->location.lat();
|
||||
_last_lng = gps->location.lng();
|
||||
_last_lat = gps->location.lat();
|
||||
_last_lng = gps->location.lng();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -243,11 +243,12 @@ double P082_data_struct::distanceSinceLast(unsigned int maxAge_msec) {
|
||||
// @param age is the time in msec since the last update of the time +
|
||||
// additional centiseconds given by the GPS.
|
||||
bool P082_data_struct::getDateTime(
|
||||
struct tm& dateTime,
|
||||
uint32_t& age,
|
||||
bool& updated,
|
||||
bool& pps_sync) {
|
||||
struct tm& dateTime,
|
||||
uint32_t & age,
|
||||
bool & updated,
|
||||
bool & pps_sync) {
|
||||
updated = false;
|
||||
|
||||
if (!isInitialized()) {
|
||||
return false;
|
||||
}
|
||||
@@ -257,9 +258,9 @@ bool P082_data_struct::getDateTime(
|
||||
}
|
||||
|
||||
if (_pps_time != 0) {
|
||||
age = timePassedSince(_pps_time);
|
||||
age = timePassedSince(_pps_time);
|
||||
_pps_time = 0;
|
||||
pps_sync = true;
|
||||
pps_sync = true;
|
||||
|
||||
if ((age > P082_TIMESTAMP_AGE) || (gps->time.age() > age)) {
|
||||
return false;
|
||||
@@ -370,7 +371,7 @@ bool P082_data_struct::setDynamicModel(P082_DynamicModel model) {
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
@@ -406,7 +407,7 @@ bool P082_data_struct::writeToGPS(const uint8_t* data, size_t size) {
|
||||
if (size != easySerial->write(data, size)) {
|
||||
addLog(LOG_LEVEL_ERROR, F("GPS : Written less bytes than expected"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -414,4 +415,89 @@ bool P082_data_struct::writeToGPS(const uint8_t* data, size_t size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
# if FEATURE_PLUGIN_STATS
|
||||
bool P082_data_struct::webformLoad_show_stats(struct EventStruct *event, uint8_t var_index, P082_query query_type)
|
||||
{
|
||||
bool somethingAdded = false;
|
||||
|
||||
const PluginStats *stats = getPluginStats(var_index);
|
||||
|
||||
|
||||
if (stats != nullptr) {
|
||||
if (stats->webformLoad_show_avg(event)) {
|
||||
somethingAdded = true;
|
||||
}
|
||||
|
||||
bool show_custom = false;
|
||||
double dist_p2p = 0.0;
|
||||
double dist_stddev = 0.0;
|
||||
|
||||
if (gps != nullptr) {
|
||||
switch (query_type) {
|
||||
case P082_query::P082_QUERY_LAT:
|
||||
show_custom = true;
|
||||
// Compute distance between min and max peak
|
||||
dist_p2p = gps->distanceBetween(
|
||||
stats->getPeakLow(), _last_lng,
|
||||
stats->getPeakHigh(), _last_lng);
|
||||
dist_stddev = gps->distanceBetween(
|
||||
_last_lat, _last_lng,
|
||||
_last_lat + stats->getSampleStdDev(), _last_lng);
|
||||
break;
|
||||
case P082_query::P082_QUERY_LONG:
|
||||
show_custom = true;
|
||||
// Compute distance between min and max peak
|
||||
dist_p2p = gps->distanceBetween(
|
||||
_last_lat, stats->getPeakLow(),
|
||||
_last_lat, stats->getPeakHigh());
|
||||
// Compute distance for std.dev
|
||||
dist_stddev = gps->distanceBetween(
|
||||
_last_lat, _last_lng,
|
||||
_last_lat, _last_lng + stats->getSampleStdDev());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Only show standard deviation in meters, which is more useful than std. dev in degrees.
|
||||
if (somethingAdded) {
|
||||
if (show_custom) {
|
||||
stats->webformLoad_show_val(
|
||||
event,
|
||||
F(" std. dev"),
|
||||
dist_stddev,
|
||||
F("m"));
|
||||
} else {
|
||||
stats->webformLoad_show_stdev(event);
|
||||
}
|
||||
}
|
||||
|
||||
if (stats->webformLoad_show_peaks(event, !show_custom)) {
|
||||
somethingAdded = true;
|
||||
|
||||
if (show_custom) {
|
||||
stats->webformLoad_show_val(
|
||||
event,
|
||||
F(" Peak-to-peak coordinates"),
|
||||
stats->getPeakHigh() - stats->getPeakLow(),
|
||||
F("deg"));
|
||||
stats->webformLoad_show_val(
|
||||
event,
|
||||
F(" Peak-to-peak distance"),
|
||||
dist_p2p,
|
||||
F("m"));
|
||||
}
|
||||
}
|
||||
|
||||
if (somethingAdded) {
|
||||
addFormSeparator(4);
|
||||
}
|
||||
}
|
||||
return somethingAdded;
|
||||
}
|
||||
|
||||
# endif // if FEATURE_PLUGIN_STATS
|
||||
|
||||
|
||||
#endif // ifdef USES_P082
|
||||
|
||||
@@ -108,6 +108,10 @@ struct P082_data_struct : public PluginTaskData_base {
|
||||
bool setDynamicModel(P082_DynamicModel model);
|
||||
#endif
|
||||
|
||||
# if FEATURE_PLUGIN_STATS
|
||||
bool webformLoad_show_stats(struct EventStruct *event, uint8_t var_index, P082_query query_type);
|
||||
# endif // if FEATURE_PLUGIN_STATS
|
||||
|
||||
private:
|
||||
#ifdef P082_USE_U_BLOX_SPECIFIC
|
||||
// Compute checksum
|
||||
@@ -134,15 +138,14 @@ public:
|
||||
double _distance = 0.0;
|
||||
|
||||
|
||||
|
||||
unsigned long _pps_time = 0;
|
||||
unsigned long _last_measurement = 0;
|
||||
uint32_t _last_time = 0;
|
||||
uint32_t _last_date = 0;
|
||||
uint32_t _last_setSystemTime = 0;
|
||||
uint32_t _start_sentence = 0;
|
||||
uint32_t _start_prev_sentence = 0;
|
||||
uint32_t _start_sequence = 0;
|
||||
unsigned long _pps_time = 0;
|
||||
unsigned long _last_measurement = 0;
|
||||
uint32_t _last_time = 0;
|
||||
uint32_t _last_date = 0;
|
||||
uint32_t _last_setSystemTime = 0;
|
||||
uint32_t _start_sentence = 0;
|
||||
uint32_t _start_prev_sentence = 0;
|
||||
uint32_t _start_sequence = 0;
|
||||
# ifdef P082_SEND_GPS_TO_LOG
|
||||
String _lastSentence;
|
||||
String _currentSentence;
|
||||
|
||||
Reference in New Issue
Block a user