LCOV - code coverage report
Current view: top level - src - us_processor.cpp (source / functions) Coverage Total Hit
Test: Ultrasonic Sensor Unified Coverage Lines: 100.0 % 59 59
Test Date: 2026-02-26 16:57:45 Functions: 100.0 % 4 4
Branches: 100.0 % 38 38

             Branch data     Line data    Source code
       1                 :             : #include "us_processor.hpp"
       2                 :             : #include <algorithm>
       3                 :             : #include <cmath>
       4                 :             : #include <cstddef>
       5                 :             : #include <cstdint>
       6                 :             : 
       7                 :             : #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
       8                 :             : #include "esp_log.h"
       9                 :             : 
      10                 :             : static const char *TAG = "UsProcessor";
      11                 :             : 
      12                 :             : // Ratio thresholds for ping validity
      13                 :             : static constexpr float VALID_PING_RATIO = 0.7f;   // >= this → OK quality
      14                 :             : static constexpr float INVALID_PING_RATIO = 0.4f; // <  this → INSUFFICIENT_SAMPLES
      15                 :             : 
      16                 :             : // Fraction of max_dev_cm that triggers WEAK_SIGNAL (instead of OK)
      17                 :             : static constexpr float WEAK_VARIANCE_RATIO = 0.6f;
      18                 :             : 
      19                 :             : // Dominant cluster parameters
      20                 :             : static constexpr float CLUSTER_DELTA_CM = 5.0f; // max spread within a cluster
      21                 :             : static constexpr size_t CLUSTER_MIN_SIZE = 2;   // minimum cluster size to be considered
      22                 :             : 
      23                 :          18 : Reading UsProcessor::process(const Reading *pings, uint8_t total_pings, const UsConfig &cfg)
      24                 :             : {
      25         [ +  + ]:          18 :     if (total_pings == 0) {
      26                 :           2 :         return {UsResult::INSUFFICIENT_SAMPLES, 0.0f};
      27                 :             :     }
      28                 :             : 
      29                 :             :     float samples[MAX_PINGS];
      30                 :             :     uint8_t valid_count = 0;
      31                 :             :     uint8_t timeouts = 0;
      32                 :             :     uint8_t out_of_range = 0;
      33                 :             : 
      34                 :             :     // 1. Extract valid samples and count specific errors
      35         [ +  + ]:         114 :     for (uint8_t i = 0; i < total_pings; i++) {
      36         [ +  + ]:          98 :         if (is_success(pings[i].result)) {
      37                 :          58 :             samples[valid_count++] = pings[i].cm;
      38                 :             :         }
      39         [ +  + ]:          40 :         else if (pings[i].result == UsResult::TIMEOUT) {
      40                 :          23 :             timeouts++;
      41                 :             :         }
      42         [ +  + ]:          17 :         else if (pings[i].result == UsResult::OUT_OF_RANGE) {
      43                 :           9 :             out_of_range++;
      44                 :             :         }
      45                 :             :     }
      46                 :             : 
      47                 :             :     // 2. Compute valid ping ratio
      48                 :          16 :     float ratio = static_cast<float>(valid_count) / total_pings;
      49                 :             : 
      50                 :             :     // 3. Check minimum data threshold
      51         [ +  + ]:          16 :     if (ratio < INVALID_PING_RATIO) {
      52                 :           5 :         ESP_LOGD(TAG, "Insufficient samples: ratio=%.2f (need >= %.2f)", ratio, INVALID_PING_RATIO);
      53                 :             : 
      54                 :             :         // Refine the error based on what happened during pings
      55         [ +  + ]:           5 :         if (out_of_range >= timeouts && out_of_range > 0) {
      56                 :           2 :             return {UsResult::OUT_OF_RANGE, 0.0f};
      57                 :             :         }
      58         [ +  + ]:           3 :         if (timeouts > 0) {
      59                 :           2 :             return {UsResult::TIMEOUT, 0.0f};
      60                 :             :         }
      61                 :           1 :         return {UsResult::INSUFFICIENT_SAMPLES, 0.0f};
      62                 :             :     }
      63                 :             : 
      64                 :             :     // 4. Check variance
      65                 :          11 :     float std_dev = get_std_dev(samples, valid_count);
      66         [ +  + ]:          11 :     if (std_dev > cfg.max_dev_cm) {
      67                 :           1 :         ESP_LOGD(TAG, "High variance: std_dev=%.2f cm (limit=%.2f cm)", std_dev, cfg.max_dev_cm);
      68                 :           1 :         return {UsResult::HIGH_VARIANCE, 0.0f};
      69                 :             :     }
      70                 :             : 
      71                 :             :     // 5. Apply filter
      72                 :          10 :     float distance_cm;
      73         [ +  + ]:          10 :     if (cfg.filter == Filter::MEDIAN) {
      74                 :           6 :         distance_cm = reduce_median(samples, valid_count);
      75                 :             :     }
      76                 :             :     else {
      77                 :           4 :         distance_cm = reduce_dominant_cluster(samples, valid_count);
      78                 :             :     }
      79                 :             : 
      80                 :             :     // 6. Determine quality based on ping ratio
      81         [ +  + ]:          10 :     if (ratio >= VALID_PING_RATIO) {
      82                 :             :         // Good ratio — check if variance is elevated (but still within limit)
      83         [ +  + ]:           8 :         if (std_dev > cfg.max_dev_cm * WEAK_VARIANCE_RATIO) {
      84                 :           2 :             ESP_LOGD(TAG, "Weak signal (high ratio, elevated variance): std_dev=%.2f", std_dev);
      85                 :           2 :             return {UsResult::WEAK_SIGNAL, distance_cm};
      86                 :             :         }
      87                 :           6 :         return {UsResult::OK, distance_cm};
      88                 :             :     }
      89                 :             : 
      90                 :             :     // Ratio is between INVALID and VALID thresholds → WEAK_SIGNAL
      91                 :           2 :     ESP_LOGD(TAG, "Weak signal (low ratio): ratio=%.2f", ratio);
      92                 :           2 :     return {UsResult::WEAK_SIGNAL, distance_cm};
      93                 :             : }
      94                 :             : 
      95                 :           7 : float UsProcessor::reduce_median(float *v, std::size_t n)
      96                 :             : {
      97                 :           7 :     std::sort(v, v + n);
      98                 :           7 :     return v[n / 2];
      99                 :             : }
     100                 :             : 
     101                 :           4 : float UsProcessor::reduce_dominant_cluster(float *v, std::size_t n)
     102                 :             : {
     103                 :           4 :     std::sort(v, v + n);
     104                 :             : 
     105                 :           4 :     float best_cluster_sum = 0.0f;
     106                 :           4 :     size_t best_cluster_size = 0;
     107                 :             : 
     108         [ +  + ]:          24 :     for (size_t i = 0; i < n; i++) {
     109                 :             :         float current_sum = 0.0f;
     110                 :             :         size_t current_size = 0;
     111                 :             : 
     112         [ +  + ]:          67 :         for (size_t j = i; j < n; j++) {
     113         [ +  + ]:          55 :             if (std::abs(v[j] - v[i]) <= CLUSTER_DELTA_CM) {
     114                 :          47 :                 current_sum += v[j];
     115                 :          47 :                 current_size++;
     116                 :             :             }
     117                 :             :             else {
     118                 :             :                 break; // array is sorted, no need to continue
     119                 :             :             }
     120                 :             :         }
     121                 :             : 
     122         [ +  + ]:          20 :         if (current_size >= CLUSTER_MIN_SIZE && current_size > best_cluster_size) {
     123                 :           3 :             best_cluster_size = current_size;
     124                 :           3 :             best_cluster_sum = current_sum;
     125                 :             :         }
     126                 :             :     }
     127                 :             : 
     128         [ +  + ]:           4 :     if (best_cluster_size == 0) {
     129                 :           1 :         ESP_LOGW(TAG, "No valid cluster found, falling back to median");
     130                 :           1 :         return reduce_median(v, n);
     131                 :             :     }
     132                 :             : 
     133                 :           3 :     return best_cluster_sum / static_cast<float>(best_cluster_size);
     134                 :             : }
     135                 :             : 
     136                 :          11 : float UsProcessor::get_std_dev(const float *samples, uint8_t count)
     137                 :             : {
     138                 :          11 :     float mean = 0.0f;
     139         [ +  + ]:          65 :     for (uint8_t i = 0; i < count; i++) mean += samples[i];
     140                 :          11 :     mean /= count;
     141                 :             : 
     142                 :          11 :     float variance = 0.0f;
     143         [ +  + ]:          65 :     for (uint8_t i = 0; i < count; i++) variance += std::pow(samples[i] - mean, 2);
     144                 :          11 :     return std::sqrt(variance / count);
     145                 :             : }
        

Generated by: LCOV version 2.0-1