Fix numeric threshold

There was an issue when sensor value reach threshold set by the user.
Example: given numeric threshold, set as increasing with value of 20.0,
and following sensor updates: 20 -> 30 -> 40, trigger action  would not
start, which is clearly a defect, as 30 is crossed.

Testing done:
- numeric threshold now works with example given above.
- UTs were updated and are passing.

Signed-off-by: Szymon Dompke <szymon.dompke@intel.com>
Change-Id: I955876e076f2286efd98d187cae775a7ab2f4e28
diff --git a/src/discrete_threshold.cpp b/src/discrete_threshold.cpp
index 2acb2bf..ad3b153 100644
--- a/src/discrete_threshold.cpp
+++ b/src/discrete_threshold.cpp
@@ -43,14 +43,15 @@
 std::shared_ptr<DiscreteThreshold::ThresholdDetail>
     DiscreteThreshold::makeDetails(const std::string& sensorName)
 {
-    return std::make_shared<ThresholdDetail>(sensorName, false, ioc);
+    return std::make_shared<ThresholdDetail>(sensorName, ioc);
 }
 
 void DiscreteThreshold::sensorUpdated(interfaces::Sensor& sensor,
                                       Milliseconds timestamp, double value)
 {
     auto& details = getDetails(sensor);
-    auto& [sensorName, dwell, timer] = details;
+    auto& dwell = details.dwell;
+    auto& timer = details.timer;
 
     if (dwell && value != numericThresholdValue)
     {
@@ -66,7 +67,7 @@
 void DiscreteThreshold::startTimer(DiscreteThreshold::ThresholdDetail& details,
                                    double value)
 {
-    const auto& sensorName = details.sensorName;
+    auto& sensorName = details.getSensorName();
     auto& dwell = details.dwell;
     auto& timer = details.timer;
 
diff --git a/src/discrete_threshold.hpp b/src/discrete_threshold.hpp
index 48510de..3b0d101 100644
--- a/src/discrete_threshold.hpp
+++ b/src/discrete_threshold.hpp
@@ -51,17 +51,24 @@
 
     struct ThresholdDetail
     {
-        std::string sensorName;
-        bool dwell;
+        bool dwell = false;
         boost::asio::steady_timer timer;
 
-        ThresholdDetail(const std::string& name, bool dwell,
+        ThresholdDetail(const std::string& sensorNameIn,
                         boost::asio::io_context& ioc) :
-            sensorName(name),
-            dwell(dwell), timer(ioc)
+            timer(ioc),
+            sensorName(sensorNameIn)
         {}
         ThresholdDetail(const ThresholdDetail&) = delete;
         ThresholdDetail(ThresholdDetail&&) = delete;
+
+        const std::string& getSensorName()
+        {
+            return sensorName;
+        }
+
+      private:
+        std::string sensorName;
     };
     using SensorDetails =
         std::unordered_map<std::shared_ptr<interfaces::Sensor>,
diff --git a/src/numeric_threshold.cpp b/src/numeric_threshold.cpp
index 24683a2..0b60dac 100644
--- a/src/numeric_threshold.cpp
+++ b/src/numeric_threshold.cpp
@@ -39,37 +39,60 @@
 std::shared_ptr<NumericThreshold::ThresholdDetail>
     NumericThreshold::makeDetails(const std::string& sensorName)
 {
-    return std::make_shared<ThresholdDetail>(sensorName, thresholdValue, false,
-                                             ioc);
+    return std::make_shared<ThresholdDetail>(sensorName, ioc);
 }
 
 void NumericThreshold::sensorUpdated(interfaces::Sensor& sensor,
                                      Milliseconds timestamp, double value)
 {
     auto& details = getDetails(sensor);
-    auto& [sensorName, prevValue, dwell, timer] = details;
-    bool decreasing = thresholdValue < prevValue && thresholdValue > value;
-    bool increasing = thresholdValue > prevValue && thresholdValue < value;
+    auto& prevValue = details.prevValue;
+    auto& prevDirection = details.prevDirection;
+    auto& dwell = details.dwell;
+    auto& timer = details.timer;
 
-    if (dwell && (increasing || decreasing))
+    if (!prevValue)
+    {
+        prevValue = value;
+        return;
+    }
+
+    bool crossedDecreasing =
+        thresholdValue < prevValue && thresholdValue > value;
+    bool crossedIncreasing =
+        thresholdValue > prevValue && thresholdValue < value;
+
+    if (!crossedDecreasing && !crossedIncreasing && thresholdValue == prevValue)
+    {
+        crossedDecreasing = prevDirection == numeric::Direction::decreasing &&
+                            thresholdValue > value;
+        crossedIncreasing = prevDirection == numeric::Direction::increasing &&
+                            thresholdValue < value;
+    }
+
+    if (dwell && (crossedIncreasing || crossedDecreasing))
     {
         timer.cancel();
         dwell = false;
     }
-    if ((direction == numeric::Direction::decreasing && decreasing) ||
-        (direction == numeric::Direction::increasing && increasing) ||
-        (direction == numeric::Direction::either && (increasing || decreasing)))
+    if ((direction == numeric::Direction::decreasing && crossedDecreasing) ||
+        (direction == numeric::Direction::increasing && crossedIncreasing) ||
+        (direction == numeric::Direction::either &&
+         (crossedIncreasing || crossedDecreasing)))
     {
         startTimer(details, value);
     }
 
+    prevDirection = value > prevValue   ? numeric::Direction::increasing
+                    : value < prevValue ? numeric::Direction::decreasing
+                                        : numeric::Direction::either;
     prevValue = value;
 }
 
 void NumericThreshold::startTimer(NumericThreshold::ThresholdDetail& details,
                                   double value)
 {
-    const auto& sensorName = details.sensorName;
+    auto& sensorName = details.getSensorName();
     auto& dwell = details.dwell;
     auto& timer = details.timer;
 
diff --git a/src/numeric_threshold.hpp b/src/numeric_threshold.hpp
index 4e47721..4b861a3 100644
--- a/src/numeric_threshold.hpp
+++ b/src/numeric_threshold.hpp
@@ -50,18 +50,26 @@
 
     struct ThresholdDetail
     {
-        std::string sensorName;
-        double prevValue;
-        bool dwell;
+        std::optional<double> prevValue = std::nullopt;
+        numeric::Direction prevDirection = numeric::Direction::either;
+        bool dwell = false;
         boost::asio::steady_timer timer;
 
-        ThresholdDetail(const std::string& name, double prevValue, bool dwell,
+        ThresholdDetail(const std::string& sensorNameIn,
                         boost::asio::io_context& ioc) :
-            sensorName(name),
-            prevValue(prevValue), dwell(dwell), timer(ioc)
+            timer(ioc),
+            sensorName(sensorNameIn)
         {}
         ThresholdDetail(const ThresholdDetail&) = delete;
         ThresholdDetail(ThresholdDetail&&) = delete;
+
+        const std::string& getSensorName()
+        {
+            return sensorName;
+        }
+
+      private:
+        std::string sensorName;
     };
     using SensorDetails =
         std::unordered_map<std::shared_ptr<interfaces::Sensor>,
diff --git a/tests/src/test_numeric_threshold.cpp b/tests/src/test_numeric_threshold.cpp
index 69ad0de..902e72f 100644
--- a/tests/src/test_numeric_threshold.cpp
+++ b/tests/src/test_numeric_threshold.cpp
@@ -320,7 +320,43 @@
                                     .Direction(numeric::Direction::either)
                                     .InitialValues({100.0, 80.0})
                                     .Updates({{0, 85.0}, {1, 91.0}})
-                                    .Expected({{0, 85.0}, {1, 91.0}})));
+                                    .Expected({{0, 85.0}, {1, 91.0}}),
+                                NumericParams()
+                                    .ThresholdValue(30.0)
+                                    .Direction(numeric::Direction::decreasing)
+                                    .InitialValues({40.0})
+                                    .Updates({{0, 30.0}, {0, 20.0}})
+                                    .Expected({{0, 20.0}}),
+                                NumericParams()
+                                    .ThresholdValue(30.0)
+                                    .Direction(numeric::Direction::decreasing)
+                                    .InitialValues({20.0})
+                                    .Updates({{0, 30.0}, {0, 20.0}})
+                                    .Expected({}),
+                                NumericParams()
+                                    .ThresholdValue(30.0)
+                                    .Direction(numeric::Direction::either)
+                                    .InitialValues({20.0})
+                                    .Updates({{0, 30.0}, {0, 20.0}})
+                                    .Expected({}),
+                                NumericParams()
+                                    .ThresholdValue(30.0)
+                                    .Direction(numeric::Direction::increasing)
+                                    .InitialValues({20.0})
+                                    .Updates({{0, 30.0}, {0, 40.0}})
+                                    .Expected({{0, 40.0}}),
+                                NumericParams()
+                                    .ThresholdValue(30.0)
+                                    .Direction(numeric::Direction::increasing)
+                                    .InitialValues({40.0})
+                                    .Updates({{0, 30.0}, {0, 40.0}})
+                                    .Expected({}),
+                                NumericParams()
+                                    .ThresholdValue(30.0)
+                                    .Direction(numeric::Direction::either)
+                                    .InitialValues({40.0})
+                                    .Updates({{0, 30.0}, {0, 40.0}})
+                                    .Expected({})));
 
 TEST_P(TestNumericThresholdNoDwellTime, senorsIsUpdatedMultipleTimes)
 {