From aff9e626899dec8dba3ce28a35f6254439fbb233 Mon Sep 17 00:00:00 2001
From: GreenWizard <sizeof.2011@gmail.com>
Date: Thu, 28 Dec 2023 12:34:20 +0100
Subject: [PATCH 01/21] Basic impl. with a bugs

---
 .../lib/RemoteControl/RemoteControl.cpp       |  8 +++
 .../lib/RemoteControl/RemoteControl.h         |  1 +
 .../tea_poor/lib/WaterPump/IWaterPump.h       | 15 ++++
 .../WaterPumpController.cpp                   | 19 ++---
 .../lib/WaterPump/WaterPumpController.h       | 23 +++++++
 .../lib/WaterPump/WaterPumpScheduler.cpp      | 36 ++++++++++
 .../lib/WaterPump/WaterPumpScheduler.h        | 33 +++++++++
 .../WaterPumpController/WaterPumpController.h | 28 --------
 controller/tea_poor/platformio.ini            |  5 ++
 controller/tea_poor/src/main.cpp              | 60 +++++++++++++---
 .../test_local/WaterPumpScheduler_test.cpp    | 69 +++++++++++++++++++
 11 files changed, 245 insertions(+), 52 deletions(-)
 create mode 100644 controller/tea_poor/lib/WaterPump/IWaterPump.h
 rename controller/tea_poor/lib/{WaterPumpController => WaterPump}/WaterPumpController.cpp (66%)
 create mode 100644 controller/tea_poor/lib/WaterPump/WaterPumpController.h
 create mode 100644 controller/tea_poor/lib/WaterPump/WaterPumpScheduler.cpp
 create mode 100644 controller/tea_poor/lib/WaterPump/WaterPumpScheduler.h
 delete mode 100644 controller/tea_poor/lib/WaterPumpController/WaterPumpController.h
 create mode 100644 controller/tea_poor/test/test_local/WaterPumpScheduler_test.cpp

diff --git a/controller/tea_poor/lib/RemoteControl/RemoteControl.cpp b/controller/tea_poor/lib/RemoteControl/RemoteControl.cpp
index c4ebe9d..16c3ec9 100644
--- a/controller/tea_poor/lib/RemoteControl/RemoteControl.cpp
+++ b/controller/tea_poor/lib/RemoteControl/RemoteControl.cpp
@@ -95,4 +95,12 @@ void RemoteControl::process() {
     _app.process(&client);
     client.stop();
   }
+}
+
+String RemoteControl::asJSONString() const {
+  String result = "{";
+  result += "\"SSID\": \"" + _SSID + "\",";
+  result += "\"signal strength\": " + String(WiFi.RSSI());
+  result += "}";
+  return result;
 }
\ No newline at end of file
diff --git a/controller/tea_poor/lib/RemoteControl/RemoteControl.h b/controller/tea_poor/lib/RemoteControl/RemoteControl.h
index 78fb5e5..1447248 100644
--- a/controller/tea_poor/lib/RemoteControl/RemoteControl.h
+++ b/controller/tea_poor/lib/RemoteControl/RemoteControl.h
@@ -14,6 +14,7 @@ class RemoteControl {
   ~RemoteControl();
   void setup(RemoteControlRoutesCallback routes);
   void process();
+  String asJSONString() const;
 private:
   const String _SSID;
   const String _SSIDPassword;
diff --git a/controller/tea_poor/lib/WaterPump/IWaterPump.h b/controller/tea_poor/lib/WaterPump/IWaterPump.h
new file mode 100644
index 0000000..4377980
--- /dev/null
+++ b/controller/tea_poor/lib/WaterPump/IWaterPump.h
@@ -0,0 +1,15 @@
+#ifndef IWATERPUMP_H
+#define IWATERPUMP_H
+
+class IWaterPump {
+public:
+  virtual ~IWaterPump() {}
+
+  virtual void setup() = 0;
+  virtual void start() = 0;
+  virtual void stop() = 0;
+
+  virtual bool isRunning() const = 0;
+};
+
+#endif
\ No newline at end of file
diff --git a/controller/tea_poor/lib/WaterPumpController/WaterPumpController.cpp b/controller/tea_poor/lib/WaterPump/WaterPumpController.cpp
similarity index 66%
rename from controller/tea_poor/lib/WaterPumpController/WaterPumpController.cpp
rename to controller/tea_poor/lib/WaterPump/WaterPumpController.cpp
index b8ce60f..25768ec 100644
--- a/controller/tea_poor/lib/WaterPumpController/WaterPumpController.cpp
+++ b/controller/tea_poor/lib/WaterPump/WaterPumpController.cpp
@@ -15,24 +15,17 @@ void WaterPumpController::setup() {
   pinMode(_directionPin, OUTPUT);
   pinMode(_brakePin, OUTPUT);
   pinMode(_powerPin, OUTPUT);
-  // TODO: check that its okay to do during setup
-  stopPump();
+  stop();
 }
 
-void WaterPumpController::pour(int milliseconds) {
-  startPump();
-  delay(milliseconds);
-  stopPump();
-}
-
-void WaterPumpController::startPump() {
-  _state = PUMP_ON;
+void WaterPumpController::start() {
+  _isRunning = true;
   digitalWrite(_brakePin, LOW); // release breaks
   analogWrite(_powerPin, 255);
 }
 
-void WaterPumpController::stopPump() {
+void WaterPumpController::stop() {
   digitalWrite(_brakePin, HIGH); // activate breaks
   analogWrite(_powerPin, 0);
-  _state = PUMP_OFF;
-}
+  _isRunning = false;
+}
\ No newline at end of file
diff --git a/controller/tea_poor/lib/WaterPump/WaterPumpController.h b/controller/tea_poor/lib/WaterPump/WaterPumpController.h
new file mode 100644
index 0000000..511560c
--- /dev/null
+++ b/controller/tea_poor/lib/WaterPump/WaterPumpController.h
@@ -0,0 +1,23 @@
+#ifndef WATERPUMPCONTROLLER_H
+#define WATERPUMPCONTROLLER_H
+#include "IWaterPump.h"
+
+class WaterPumpController: public IWaterPump {
+private:
+  const int _directionPin;
+  const int _brakePin;
+  const int _powerPin;
+  const int _maxPower = 255;
+  bool _isRunning = false;
+public:
+  WaterPumpController(int directionPin, int brakePin, int powerPin);
+  virtual ~WaterPumpController() override;
+  
+  virtual void setup() override;
+  virtual void start() override;
+  virtual void stop() override;
+
+  virtual bool isRunning() const override { return _isRunning; }
+};
+
+#endif
\ No newline at end of file
diff --git a/controller/tea_poor/lib/WaterPump/WaterPumpScheduler.cpp b/controller/tea_poor/lib/WaterPump/WaterPumpScheduler.cpp
new file mode 100644
index 0000000..21bb9fe
--- /dev/null
+++ b/controller/tea_poor/lib/WaterPump/WaterPumpScheduler.cpp
@@ -0,0 +1,36 @@
+#include "WaterPumpScheduler.h"
+
+WaterPumpScheduler::WaterPumpScheduler(IWaterPump* waterPump, unsigned long forceStopIntervalMs) {
+}
+
+WaterPumpScheduler::~WaterPumpScheduler() {
+  delete _waterPump;
+}
+
+void WaterPumpScheduler::setup() {
+  _waterPump->setup();
+}
+
+void WaterPumpScheduler::start(unsigned long runTimeMs, unsigned long currentTimeMs) {
+  _stopTime = currentTimeMs + runTimeMs;
+  _waterPump->start();
+}
+
+void WaterPumpScheduler::stop() {
+  _waterPump->stop();
+  _stopTime = 0; // a bit of paranoia :)
+}
+
+void WaterPumpScheduler::tick(unsigned long currentTimeMs) {
+  if (_stopTime <= currentTimeMs) {
+    stop();
+    _stopTime = currentTimeMs + _forceStopIntervalMs; // force stop after X milliseconds
+  }
+}
+
+WaterPumpScheduler::WaterPumpStatus WaterPumpScheduler::status() {
+  return {
+    _waterPump->isRunning(),
+    _stopTime
+  };
+}
\ No newline at end of file
diff --git a/controller/tea_poor/lib/WaterPump/WaterPumpScheduler.h b/controller/tea_poor/lib/WaterPump/WaterPumpScheduler.h
new file mode 100644
index 0000000..6abb21f
--- /dev/null
+++ b/controller/tea_poor/lib/WaterPump/WaterPumpScheduler.h
@@ -0,0 +1,33 @@
+#ifndef WATERPUMPSCHEDULER_H
+#define WATERPUMPSCHEDULER_H
+
+#include "IWaterPump.h"
+
+// This class is responsible for scheduling water pump
+// It is used to make sure that water pump is running for a limited time
+// It is also ensuring that water pump is stopped if not needed
+class WaterPumpScheduler {
+private:
+  IWaterPump* _waterPump;
+  unsigned long _stopTime = 0;
+  // each X milliseconds will force stop water pump
+  unsigned long _forceStopIntervalMs;
+public:
+  WaterPumpScheduler(IWaterPump* waterPump, unsigned long forceStopIntervalMs);
+  WaterPumpScheduler(IWaterPump* waterPump) : WaterPumpScheduler(waterPump, 1000) {}
+  ~WaterPumpScheduler();
+
+  void setup();
+  void stop();
+  // for simplicity and testability we are passing current time as parameter
+  void start(unsigned long runTimeMs, unsigned long currentTimeMs);
+  void tick(unsigned long currentTimeMs);
+
+  // pump status
+  struct WaterPumpStatus {
+    bool isRunning;
+    unsigned long stopTime;
+  };
+  WaterPumpStatus status();
+};
+#endif
\ No newline at end of file
diff --git a/controller/tea_poor/lib/WaterPumpController/WaterPumpController.h b/controller/tea_poor/lib/WaterPumpController/WaterPumpController.h
deleted file mode 100644
index 8561876..0000000
--- a/controller/tea_poor/lib/WaterPumpController/WaterPumpController.h
+++ /dev/null
@@ -1,28 +0,0 @@
-#ifndef WATERPUMPCONTROLLER_H
-#define WATERPUMPCONTROLLER_H
-
-class WaterPumpController {
-public:
-  enum EPumpState {
-    PUMP_OFF,
-    PUMP_ON
-  };
-private:
-  const int _directionPin;
-  const int _brakePin;
-  const int _powerPin;
-  const int _maxPower = 255;
-  EPumpState _state = PUMP_OFF;
-public:
-  WaterPumpController(int directionPin, int brakePin, int powerPin);
-  ~WaterPumpController();
-
-  void setup();
-  void pour(int miliseconds);
-  void startPump();
-  void stopPump();
-
-  EPumpState state() const { return _state; }
-};
-
-#endif // WATERPUMPCONTROLLER_H
diff --git a/controller/tea_poor/platformio.ini b/controller/tea_poor/platformio.ini
index 1515112..ca12eab 100644
--- a/controller/tea_poor/platformio.ini
+++ b/controller/tea_poor/platformio.ini
@@ -14,3 +14,8 @@ board = uno_r4_wifi
 framework = arduino
 lib_deps = 
 	lasselukkari/aWOT@^3.5.0
+test_ignore = local
+
+[env:local]
+platform = native
+test_framework = googletest
\ No newline at end of file
diff --git a/controller/tea_poor/src/main.cpp b/controller/tea_poor/src/main.cpp
index 87b8358..82747dd 100644
--- a/controller/tea_poor/src/main.cpp
+++ b/controller/tea_poor/src/main.cpp
@@ -1,9 +1,12 @@
 #include <Arduino.h>
 #include <WaterPumpController.h>
+#include <WaterPumpScheduler.h>
 #include <RemoteControl.h>
 
 // Setting up water pump
-WaterPumpController waterPumpController(12, 9, 3);
+WaterPumpScheduler waterPump(
+  new WaterPumpController(12, 9, 3)
+);
 // Just for safety reasons, we don't want to pour tea for too long
 // Their is no reason to make it configurable and add unnecessary complexity
 const int WATER_PUMP_SAFE_THRESHOLD = 10 * 1000;
@@ -14,8 +17,38 @@ RemoteControl remoteControl(
   "VerySecurePassword" // network password
 );
 
+void _sendSystemStatus(Response &res) {
+  // send system status as JSON
+  res.println("{");
+  // send water threshold
+  res.print("\"water threshold\": ");
+  res.print(WATER_PUMP_SAFE_THRESHOLD);
+  res.println(",");
+  
+  // send water pump status
+  const auto waterPumpStatus = waterPump.status();
+  res.println("\"pump\": {");
+  res.print("\"running\": ");
+  res.print(waterPumpStatus.isRunning ? "true, " : "false, ");
+  const unsigned long timeLeft = 
+    waterPumpStatus.isRunning ?
+    waterPumpStatus.stopTime - millis() :
+    0;
+  res.print("\"time left\": ");
+  res.print(timeLeft);
+  res.println("},");
+  // end of water pump status
+  ///////////////////////////////////
+  // send remote control status
+  res.print("\"remote control\": ");
+  res.print(remoteControl.asJSONString());
+  res.println();
+  // end of JSON
+  res.println("}");
+}
+
 bool isValidIntNumber(const char *str, const int maxValue, const int minValue=0) {
-  if (strlen(str) <= 0) return false;
+  if (strlen(str) < 1) return false;
   const int value = atoi(str);
   if (value < minValue) return false;
   if (maxValue <= value) return false;
@@ -31,24 +64,29 @@ void pour_tea(Request &req, Response &res) {
     res.println(WATER_PUMP_SAFE_THRESHOLD);
     return;
   }
-  const int pouringDelayMs = atoi(milliseconds);
-  // actually pour tea
-  waterPumpController.pour(pouringDelayMs);
-
-  // Serial.println(req.JSON());
-  res.print("Poured Tea in: ");
-  res.print(pouringDelayMs);
-  res.print(" milliseconds!");
+  // start pouring tea
+  waterPump.start( atoi(milliseconds), millis() );
+  _sendSystemStatus(res);
 }
 
 void setup() {
   Serial.begin(9600);
-  waterPumpController.setup();
+  waterPump.setup();
   remoteControl.setup([](Application &app) {
     app.get("/pour_tea", pour_tea);
+    // stop water pump
+    app.get("/stop", [](Request &req, Response &res) {
+      waterPump.stop();
+      _sendSystemStatus(res);
+    });
+    // get system status
+    app.get("/status", [](Request &req, Response &res) {
+      _sendSystemStatus(res);
+    });
   });
 }
 
 void loop() {
+  waterPump.tick(millis());
   remoteControl.process();
 };
\ No newline at end of file
diff --git a/controller/tea_poor/test/test_local/WaterPumpScheduler_test.cpp b/controller/tea_poor/test/test_local/WaterPumpScheduler_test.cpp
new file mode 100644
index 0000000..61d75c6
--- /dev/null
+++ b/controller/tea_poor/test/test_local/WaterPumpScheduler_test.cpp
@@ -0,0 +1,69 @@
+// I wasn't able to run tests at all. Run them locally and confirm that they are working.
+// Its either a local problem or a problem with the configuration of the project.
+// Further goes a sketch of the tests, but I wasn't able to run them.
+#include <gtest/gtest.h>
+#include <WaterPumpScheduler.h>
+
+// Fake water pump
+class FakeWaterPump : public IWaterPump {
+private:
+  bool _isRunning = false;
+public:
+  void setup() override { _isRunning = false; }
+  void start() override { _isRunning = true;  }
+  void stop() override  { _isRunning = false; }
+
+  bool isRunning() override { return _isRunning; }
+};
+// End of fake water pump
+
+// test that pump is stopping after given time
+TEST(WaterPumpScheduler, test_pump_stops_after_given_time) {
+  // random time between 1 and 10 seconds
+  const unsigned long runTimeMs = 1000 + (rand() % 10) * 1000;
+  FakeWaterPump fakeWaterPump;
+  WaterPumpScheduler waterPumpScheduler(&fakeWaterPump);
+  waterPumpScheduler.setup();
+  // start water pump
+  unsigned long currentTimeMs = 0;
+  waterPumpScheduler.start(runTimeMs, currentTimeMs);
+  // check status
+  auto status = waterPumpScheduler.status();
+  ASSERT_TRUE(status.isRunning);
+  ASSERT_EQ(status.stopTime, runTimeMs);
+
+  while (currentTimeMs < runTimeMs) {
+    waterPumpScheduler.tick(currentTimeMs);
+    ASSERT_TRUE(fakeWaterPump->isRunning());
+    currentTimeMs += 100;
+  }
+  // pump should be stopped after given time
+  waterPumpScheduler.tick(runTimeMs + 1);
+  ASSERT_FALSE(fakeWaterPump->isRunning());
+}
+
+// test that pump is periodically forced to stop after given time
+TEST(WaterPumpScheduler, test_pump_is_periodically_forced_to_stop_after_given_time) {
+  FakeWaterPump fakeWaterPump;
+  WaterPumpScheduler waterPumpScheduler(&fakeWaterPump, 1000); // force stop each 1 second
+  waterPumpScheduler.setup();
+  // start water pump
+  unsigned long currentTimeMs = 0;
+  waterPumpScheduler.start(1, currentTimeMs);
+  currentTimeMs += 1;
+  waterPumpScheduler.tick(currentTimeMs);
+  ASSERT_FALSE(fakeWaterPump->isRunning()); // pump should be stopped after given time
+
+  for(int i = 0; i < 10; i++) {
+    // emulate that pump was started again
+    fakeWaterPump.start();
+    currentTimeMs += 1000;
+    waterPumpScheduler.tick(currentTimeMs);
+    ASSERT_FALSE(fakeWaterPump->isRunning()); // pump should be stopped
+  }
+}
+
+int main(int argc, char **argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
\ No newline at end of file

From f54a37559d99e05dec32564ac8498e994e391132 Mon Sep 17 00:00:00 2001
From: GreenWizard <sizeof.2011@gmail.com>
Date: Thu, 28 Dec 2023 13:15:38 +0100
Subject: [PATCH 02/21] would this fix CI?

---
 controller/tea_poor/platformio.ini | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/controller/tea_poor/platformio.ini b/controller/tea_poor/platformio.ini
index ca12eab..6accfa8 100644
--- a/controller/tea_poor/platformio.ini
+++ b/controller/tea_poor/platformio.ini
@@ -18,4 +18,6 @@ test_ignore = local
 
 [env:local]
 platform = native
-test_framework = googletest
\ No newline at end of file
+; test_framework = googletest
+test_build_src = yes
+build_src_filter = +*_test.cpp
\ No newline at end of file

From c8c2e45b268cadf6d3551794d0436e0f2871256c Mon Sep 17 00:00:00 2001
From: GreenWizard <sizeof.2011@gmail.com>
Date: Thu, 28 Dec 2023 13:27:31 +0100
Subject: [PATCH 03/21] maybe now?

---
 controller/tea_poor/platformio.ini | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/controller/tea_poor/platformio.ini b/controller/tea_poor/platformio.ini
index 6accfa8..912817c 100644
--- a/controller/tea_poor/platformio.ini
+++ b/controller/tea_poor/platformio.ini
@@ -18,6 +18,6 @@ test_ignore = local
 
 [env:local]
 platform = native
-; test_framework = googletest
-test_build_src = yes
-build_src_filter = +*_test.cpp
\ No newline at end of file
+test_framework = googletest
+test_build_src = no
+build_src_filter = -<*.cpp> +<*_test.cpp>
\ No newline at end of file

From c6003dc1614cb296c3e6767afe6eda05e97391c1 Mon Sep 17 00:00:00 2001
From: GreenWizard <sizeof.2011@gmail.com>
Date: Thu, 28 Dec 2023 13:31:20 +0100
Subject: [PATCH 04/21] last try

---
 controller/tea_poor/platformio.ini | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/controller/tea_poor/platformio.ini b/controller/tea_poor/platformio.ini
index 912817c..2999ffc 100644
--- a/controller/tea_poor/platformio.ini
+++ b/controller/tea_poor/platformio.ini
@@ -20,4 +20,4 @@ test_ignore = local
 platform = native
 test_framework = googletest
 test_build_src = no
-build_src_filter = -<*.cpp> +<*_test.cpp>
\ No newline at end of file
+build_src_filter = +*.cpp -*_test.cpp
\ No newline at end of file

From 815c496f65fc3b96329cab3879faa4c5235ad643 Mon Sep 17 00:00:00 2001
From: GreenWizard <sizeof.2011@gmail.com>
Date: Thu, 28 Dec 2023 14:02:55 +0100
Subject: [PATCH 05/21] pls?

---
 controller/tea_poor/platformio.ini | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/controller/tea_poor/platformio.ini b/controller/tea_poor/platformio.ini
index 2999ffc..59d09e1 100644
--- a/controller/tea_poor/platformio.ini
+++ b/controller/tea_poor/platformio.ini
@@ -20,4 +20,4 @@ test_ignore = local
 platform = native
 test_framework = googletest
 test_build_src = no
-build_src_filter = +*.cpp -*_test.cpp
\ No newline at end of file
+lib_ldf_mode = deep
\ No newline at end of file

From 54b8e582586a929a48508291520cebc0a021cdad Mon Sep 17 00:00:00 2001
From: omonrise <arsenys@live.com>
Date: Fri, 29 Dec 2023 16:21:59 +0100
Subject: [PATCH 06/21] remove local and attempt to switch to unity framework

---
 controller/tea_poor/platformio.ini            |   7 +-
 .../test_local/WaterPumpScheduler_test.cpp    |  41 ++-
 .../tea_poor/test/test_local/unity_config.h   | 244 ++++++++++++++++++
 3 files changed, 274 insertions(+), 18 deletions(-)
 create mode 100644 controller/tea_poor/test/test_local/unity_config.h

diff --git a/controller/tea_poor/platformio.ini b/controller/tea_poor/platformio.ini
index 59d09e1..e711024 100644
--- a/controller/tea_poor/platformio.ini
+++ b/controller/tea_poor/platformio.ini
@@ -12,12 +12,9 @@
 platform = renesas-ra
 board = uno_r4_wifi
 framework = arduino
+test_framework = unity
 lib_deps = 
 	lasselukkari/aWOT@^3.5.0
+	throwtheswitch/Unity@^2.5.2
 test_ignore = local
 
-[env:local]
-platform = native
-test_framework = googletest
-test_build_src = no
-lib_ldf_mode = deep
\ No newline at end of file
diff --git a/controller/tea_poor/test/test_local/WaterPumpScheduler_test.cpp b/controller/tea_poor/test/test_local/WaterPumpScheduler_test.cpp
index 61d75c6..dc43712 100644
--- a/controller/tea_poor/test/test_local/WaterPumpScheduler_test.cpp
+++ b/controller/tea_poor/test/test_local/WaterPumpScheduler_test.cpp
@@ -1,9 +1,11 @@
 // I wasn't able to run tests at all. Run them locally and confirm that they are working.
 // Its either a local problem or a problem with the configuration of the project.
 // Further goes a sketch of the tests, but I wasn't able to run them.
-#include <gtest/gtest.h>
+#include <unity.h>
 #include <WaterPumpScheduler.h>
 
+
+
 // Fake water pump
 class FakeWaterPump : public IWaterPump {
 private:
@@ -13,12 +15,18 @@ class FakeWaterPump : public IWaterPump {
   void start() override { _isRunning = true;  }
   void stop() override  { _isRunning = false; }
 
-  bool isRunning() override { return _isRunning; }
+  bool isRunning() const override { return _isRunning; }
 };
 // End of fake water pump
 
+
+/* Empty functions required by unity framework*/
+void setUp() { /* Setup code here */ }
+void tearDown() { /* Teardown code here */ }
+
+
 // test that pump is stopping after given time
-TEST(WaterPumpScheduler, test_pump_stops_after_given_time) {
+void test_pump_stops_after_given_time() {
   // random time between 1 and 10 seconds
   const unsigned long runTimeMs = 1000 + (rand() % 10) * 1000;
   FakeWaterPump fakeWaterPump;
@@ -29,21 +37,21 @@ TEST(WaterPumpScheduler, test_pump_stops_after_given_time) {
   waterPumpScheduler.start(runTimeMs, currentTimeMs);
   // check status
   auto status = waterPumpScheduler.status();
-  ASSERT_TRUE(status.isRunning);
-  ASSERT_EQ(status.stopTime, runTimeMs);
+  UNITY_TEST_ASSERT_TRUE(status.isRunning);
+  UNITY_TEST_ASSERT_EQUAL(status.stopTime, runTimeMs);
 
   while (currentTimeMs < runTimeMs) {
     waterPumpScheduler.tick(currentTimeMs);
-    ASSERT_TRUE(fakeWaterPump->isRunning());
+    UNITY_TEST_ASSERT_TRUE(fakeWaterPump.isRunning());
     currentTimeMs += 100;
   }
   // pump should be stopped after given time
   waterPumpScheduler.tick(runTimeMs + 1);
-  ASSERT_FALSE(fakeWaterPump->isRunning());
+  UNITY_TEST_ASSERT_FALSE(fakeWaterPump.isRunning());
 }
 
 // test that pump is periodically forced to stop after given time
-TEST(WaterPumpScheduler, test_pump_is_periodically_forced_to_stop_after_given_time) {
+void test_pump_is_periodically_forced_to_stop_after_given_time () {
   FakeWaterPump fakeWaterPump;
   WaterPumpScheduler waterPumpScheduler(&fakeWaterPump, 1000); // force stop each 1 second
   waterPumpScheduler.setup();
@@ -52,18 +60,25 @@ TEST(WaterPumpScheduler, test_pump_is_periodically_forced_to_stop_after_given_ti
   waterPumpScheduler.start(1, currentTimeMs);
   currentTimeMs += 1;
   waterPumpScheduler.tick(currentTimeMs);
-  ASSERT_FALSE(fakeWaterPump->isRunning()); // pump should be stopped after given time
+  UNITY_TEST_ASSERT_FALSE(fakeWaterPump.isRunning()); // pump should be stopped after given time
 
   for(int i = 0; i < 10; i++) {
     // emulate that pump was started again
     fakeWaterPump.start();
     currentTimeMs += 1000;
     waterPumpScheduler.tick(currentTimeMs);
-    ASSERT_FALSE(fakeWaterPump->isRunning()); // pump should be stopped
+    UNITY_TEST_ASSERT_FALSE(fakeWaterPump.isRunning()); // pump should be stopped
   }
 }
 
-int main(int argc, char **argv) {
-  ::testing::InitGoogleTest(&argc, argv);
-  return RUN_ALL_TESTS();
+void setup() {
+  UNITY_BEGIN();
+  RUN_TEST(test_pump_stops_after_given_time);
+  RUN_TEST(test_pump_is_periodically_forced_to_stop_after_given_time);
+  UNITY_END();
+}
+
+
+void loop() {
+
 }
\ No newline at end of file
diff --git a/controller/tea_poor/test/test_local/unity_config.h b/controller/tea_poor/test/test_local/unity_config.h
new file mode 100644
index 0000000..f45e802
--- /dev/null
+++ b/controller/tea_poor/test/test_local/unity_config.h
@@ -0,0 +1,244 @@
+/* Unity Configuration
+ * As of May 11th, 2016 at ThrowTheSwitch/Unity commit 837c529
+ * Update: December 29th, 2016
+ * See Also: Unity/docs/UnityConfigurationGuide.pdf
+ *
+ * Unity is designed to run on almost anything that is targeted by a C compiler.
+ * It would be awesome if this could be done with zero configuration. While
+ * there are some targets that come close to this dream, it is sadly not
+ * universal. It is likely that you are going to need at least a couple of the
+ * configuration options described in this document.
+ *
+ * All of Unity's configuration options are `#defines`. Most of these are simple
+ * definitions. A couple are macros with arguments. They live inside the
+ * unity_internals.h header file. We don't necessarily recommend opening that
+ * file unless you really need to. That file is proof that a cross-platform
+ * library is challenging to build. From a more positive perspective, it is also
+ * proof that a great deal of complexity can be centralized primarily to one
+ * place in order to provide a more consistent and simple experience elsewhere.
+ *
+ * Using These Options
+ * It doesn't matter if you're using a target-specific compiler and a simulator
+ * or a native compiler. In either case, you've got a couple choices for
+ * configuring these options:
+ *
+ *  1. Because these options are specified via C defines, you can pass most of
+ *     these options to your compiler through command line compiler flags. Even
+ *     if you're using an embedded target that forces you to use their
+ *     overbearing IDE for all configuration, there will be a place somewhere in
+ *     your project to configure defines for your compiler.
+ *  2. You can create a custom `unity_config.h` configuration file (present in
+ *     your toolchain's search paths). In this file, you will list definitions
+ *     and macros specific to your target. All you must do is define
+ *     `UNITY_INCLUDE_CONFIG_H` and Unity will rely on `unity_config.h` for any
+ *     further definitions it may need.
+ */
+
+#ifndef UNITY_CONFIG_H
+#define UNITY_CONFIG_H
+
+/* ************************* AUTOMATIC INTEGER TYPES ***************************
+ * C's concept of an integer varies from target to target. The C Standard has
+ * rules about the `int` matching the register size of the target
+ * microprocessor. It has rules about the `int` and how its size relates to
+ * other integer types. An `int` on one target might be 16 bits while on another
+ * target it might be 64. There are more specific types in compilers compliant
+ * with C99 or later, but that's certainly not every compiler you are likely to
+ * encounter. Therefore, Unity has a number of features for helping to adjust
+ * itself to match your required integer sizes. It starts off by trying to do it
+ * automatically.
+ **************************************************************************** */
+
+/* The first attempt to guess your types is to check `limits.h`. Some compilers
+ * that don't support `stdint.h` could include `limits.h`. If you don't
+ * want Unity to check this file, define this to make it skip the inclusion.
+ * Unity looks at UINT_MAX & ULONG_MAX, which were available since C89.
+ */
+/* #define UNITY_EXCLUDE_LIMITS_H */
+
+/* The second thing that Unity does to guess your types is check `stdint.h`.
+ * This file defines `UINTPTR_MAX`, since C99, that Unity can make use of to
+ * learn about your system. It's possible you don't want it to do this or it's
+ * possible that your system doesn't support `stdint.h`. If that's the case,
+ * you're going to want to define this. That way, Unity will know to skip the
+ * inclusion of this file and you won't be left with a compiler error.
+ */
+/* #define UNITY_EXCLUDE_STDINT_H */
+
+/* ********************** MANUAL INTEGER TYPE DEFINITION ***********************
+ * If you've disabled all of the automatic options above, you're going to have
+ * to do the configuration yourself. There are just a handful of defines that
+ * you are going to specify if you don't like the defaults.
+ **************************************************************************** */
+
+ /* Define this to be the number of bits an `int` takes up on your system. The
+ * default, if not auto-detected, is 32 bits.
+ *
+ * Example:
+ */
+/* #define UNITY_INT_WIDTH 16 */
+
+/* Define this to be the number of bits a `long` takes up on your system. The
+ * default, if not autodetected, is 32 bits. This is used to figure out what
+ * kind of 64-bit support your system can handle.  Does it need to specify a
+ * `long` or a `long long` to get a 64-bit value. On 16-bit systems, this option
+ * is going to be ignored.
+ *
+ * Example:
+ */
+/* #define UNITY_LONG_WIDTH 16 */
+
+/* Define this to be the number of bits a pointer takes up on your system. The
+ * default, if not autodetected, is 32-bits. If you're getting ugly compiler
+ * warnings about casting from pointers, this is the one to look at.
+ *
+ * Example:
+ */
+/* #define UNITY_POINTER_WIDTH 64 */
+
+/* Unity will automatically include 64-bit support if it auto-detects it, or if
+ * your `int`, `long`, or pointer widths are greater than 32-bits. Define this
+ * to enable 64-bit support if none of the other options already did it for you.
+ * There can be a significant size and speed impact to enabling 64-bit support
+ * on small targets, so don't define it if you don't need it.
+ */
+/* #define UNITY_INCLUDE_64 */
+
+
+/* *************************** FLOATING POINT TYPES ****************************
+ * In the embedded world, it's not uncommon for targets to have no support for
+ * floating point operations at all or to have support that is limited to only
+ * single precision. We are able to guess integer sizes on the fly because
+ * integers are always available in at least one size. Floating point, on the
+ * other hand, is sometimes not available at all. Trying to include `float.h` on
+ * these platforms would result in an error. This leaves manual configuration as
+ * the only option.
+ **************************************************************************** */
+
+ /* By default, Unity guesses that you will want single precision floating point
+  * support, but not double precision. It's easy to change either of these using
+  * the include and exclude options here. You may include neither, just float,
+  * or both, as suits your needs.
+  */
+/* #define UNITY_EXCLUDE_FLOAT  */
+/* #define UNITY_INCLUDE_DOUBLE */
+/* #define UNITY_EXCLUDE_DOUBLE */
+
+/* For features that are enabled, the following floating point options also
+ * become available.
+ */
+
+/* Unity aims for as small of a footprint as possible and avoids most standard
+ * library calls (some embedded platforms don't have a standard library!).
+ * Because of this, its routines for printing integer values are minimalist and
+ * hand-coded. To keep Unity universal, though, we eventually chose to develop
+ * our own floating point print routines. Still, the display of floating point
+ * values during a failure are optional. By default, Unity will print the
+ * actual results of floating point assertion failures. So a failed assertion
+ * will produce a message like "Expected 4.0 Was 4.25". If you would like less
+ * verbose failure messages for floating point assertions, use this option to
+ * give a failure message `"Values Not Within Delta"` and trim the binary size.
+ */
+/* #define UNITY_EXCLUDE_FLOAT_PRINT */
+
+/* If enabled, Unity assumes you want your `FLOAT` asserts to compare standard C
+ * floats. If your compiler supports a specialty floating point type, you can
+ * always override this behavior by using this definition.
+ *
+ * Example:
+ */
+/* #define UNITY_FLOAT_TYPE float16_t */
+
+/* If enabled, Unity assumes you want your `DOUBLE` asserts to compare standard
+ * C doubles. If you would like to change this, you can specify something else
+ * by using this option. For example, defining `UNITY_DOUBLE_TYPE` to `long
+ * double` could enable gargantuan floating point types on your 64-bit processor
+ * instead of the standard `double`.
+ *
+ * Example:
+ */
+/* #define UNITY_DOUBLE_TYPE long double */
+
+/* If you look up `UNITY_ASSERT_EQUAL_FLOAT` and `UNITY_ASSERT_EQUAL_DOUBLE` as
+ * documented in the Unity Assertion Guide, you will learn that they are not
+ * really asserting that two values are equal but rather that two values are
+ * "close enough" to equal. "Close enough" is controlled by these precision
+ * configuration options. If you are working with 32-bit floats and/or 64-bit
+ * doubles (the normal on most processors), you should have no need to change
+ * these options. They are both set to give you approximately 1 significant bit
+ * in either direction. The float precision is 0.00001 while the double is
+ * 10^-12. For further details on how this works, see the appendix of the Unity
+ * Assertion Guide.
+ *
+ * Example:
+ */
+/* #define UNITY_FLOAT_PRECISION 0.001f  */
+/* #define UNITY_DOUBLE_PRECISION 0.001f */
+
+
+/* *************************** MISCELLANEOUS ***********************************
+ * Miscellaneous configuration options for Unity
+ **************************************************************************** */
+
+/* Unity uses the stddef.h header included in the C standard library for the
+ * "NULL" macro. Define this in order to disable the include of stddef.h. If you
+ * do this, you have to make sure to provide your own "NULL" definition.
+ */
+/* #define UNITY_EXCLUDE_STDDEF_H */
+
+/* Define this to enable the unity formatted print macro:
+ * "TEST_PRINTF"
+ */
+/* #define UNITY_INCLUDE_PRINT_FORMATTED */
+
+
+/* *************************** TOOLSET CUSTOMIZATION ***************************
+ * In addition to the options listed above, there are a number of other options
+ * which will come in handy to customize Unity's behavior for your specific
+ * toolchain. It is possible that you may not need to touch any of these but
+ * certain platforms, particularly those running in simulators, may need to jump
+ * through extra hoops to operate properly. These macros will help in those
+ * situations.
+ **************************************************************************** */
+
+/* By default, Unity prints its results to `stdout` as it runs. This works
+ * perfectly fine in most situations where you are using a native compiler for
+ * testing. It works on some simulators as well so long as they have `stdout`
+ * routed back to the command line. There are times, however, where the
+ * simulator will lack support for dumping results or you will want to route
+ * results elsewhere for other reasons. In these cases, you should define the
+ * `UNITY_OUTPUT_CHAR` macro. This macro accepts a single character at a time
+ * (as an `int`, since this is the parameter type of the standard C `putchar`
+ * function most commonly used). You may replace this with whatever function
+ * call you like.
+ *
+ * Example:
+ * Say you are forced to run your test suite on an embedded processor with no
+ * `stdout` option. You decide to route your test result output to a custom
+ * serial `RS232_putc()` function you wrote like thus:
+ */
+/* #define UNITY_OUTPUT_CHAR(a)                    RS232_putc(a) */
+/* #define UNITY_OUTPUT_CHAR_HEADER_DECLARATION    RS232_putc(int) */
+/* #define UNITY_OUTPUT_FLUSH()                    RS232_flush() */
+/* #define UNITY_OUTPUT_FLUSH_HEADER_DECLARATION   RS232_flush(void) */
+/* #define UNITY_OUTPUT_START()                    RS232_config(115200,1,8,0) */
+/* #define UNITY_OUTPUT_COMPLETE()                 RS232_close() */
+
+/* Some compilers require a custom attribute to be assigned to pointers, like
+ * `near` or `far`. In these cases, you can give Unity a safe default for these
+ * by defining this option with the attribute you would like.
+ *
+ * Example:
+ */
+/* #define UNITY_PTR_ATTRIBUTE __attribute__((far)) */
+/* #define UNITY_PTR_ATTRIBUTE near */
+
+/* Print execution time of each test when executed in verbose mode
+ *
+ * Example:
+ *
+ * TEST - PASS (10 ms)
+ */
+/* #define UNITY_INCLUDE_EXEC_TIME */
+
+#endif /* UNITY_CONFIG_H */
\ No newline at end of file

From 0f8ea3188e40121fa916b690a728e45b75cd8c67 Mon Sep 17 00:00:00 2001
From: Pieter Geelen <pieter@geelen.io>
Date: Fri, 29 Dec 2023 16:37:23 +0100
Subject: [PATCH 07/21] Dependency Unity for testing should not be part of
 build dependencies, unity_config.h can be removed and Test items are
 correctly referenced.

---
 controller/tea_poor/platformio.ini            |   1 -
 .../test_local/WaterPumpScheduler_test.cpp    |  12 +-
 .../tea_poor/test/test_local/unity_config.h   | 244 ------------------
 3 files changed, 6 insertions(+), 251 deletions(-)
 delete mode 100644 controller/tea_poor/test/test_local/unity_config.h

diff --git a/controller/tea_poor/platformio.ini b/controller/tea_poor/platformio.ini
index e711024..a4b4776 100644
--- a/controller/tea_poor/platformio.ini
+++ b/controller/tea_poor/platformio.ini
@@ -15,6 +15,5 @@ framework = arduino
 test_framework = unity
 lib_deps = 
 	lasselukkari/aWOT@^3.5.0
-	throwtheswitch/Unity@^2.5.2
 test_ignore = local
 
diff --git a/controller/tea_poor/test/test_local/WaterPumpScheduler_test.cpp b/controller/tea_poor/test/test_local/WaterPumpScheduler_test.cpp
index dc43712..3417fe9 100644
--- a/controller/tea_poor/test/test_local/WaterPumpScheduler_test.cpp
+++ b/controller/tea_poor/test/test_local/WaterPumpScheduler_test.cpp
@@ -37,17 +37,17 @@ void test_pump_stops_after_given_time() {
   waterPumpScheduler.start(runTimeMs, currentTimeMs);
   // check status
   auto status = waterPumpScheduler.status();
-  UNITY_TEST_ASSERT_TRUE(status.isRunning);
-  UNITY_TEST_ASSERT_EQUAL(status.stopTime, runTimeMs);
+  TEST_ASSERT_TRUE(status.isRunning);
+  TEST_ASSERT_EQUAL(status.stopTime, runTimeMs);
 
   while (currentTimeMs < runTimeMs) {
     waterPumpScheduler.tick(currentTimeMs);
-    UNITY_TEST_ASSERT_TRUE(fakeWaterPump.isRunning());
+    TEST_ASSERT_TRUE(fakeWaterPump.isRunning());
     currentTimeMs += 100;
   }
   // pump should be stopped after given time
   waterPumpScheduler.tick(runTimeMs + 1);
-  UNITY_TEST_ASSERT_FALSE(fakeWaterPump.isRunning());
+  TEST_ASSERT_FALSE(fakeWaterPump.isRunning());
 }
 
 // test that pump is periodically forced to stop after given time
@@ -60,14 +60,14 @@ void test_pump_is_periodically_forced_to_stop_after_given_time () {
   waterPumpScheduler.start(1, currentTimeMs);
   currentTimeMs += 1;
   waterPumpScheduler.tick(currentTimeMs);
-  UNITY_TEST_ASSERT_FALSE(fakeWaterPump.isRunning()); // pump should be stopped after given time
+  TEST_ASSERT_FALSE(fakeWaterPump.isRunning()); // pump should be stopped after given time
 
   for(int i = 0; i < 10; i++) {
     // emulate that pump was started again
     fakeWaterPump.start();
     currentTimeMs += 1000;
     waterPumpScheduler.tick(currentTimeMs);
-    UNITY_TEST_ASSERT_FALSE(fakeWaterPump.isRunning()); // pump should be stopped
+    TEST_ASSERT_FALSE(fakeWaterPump.isRunning()); // pump should be stopped
   }
 }
 
diff --git a/controller/tea_poor/test/test_local/unity_config.h b/controller/tea_poor/test/test_local/unity_config.h
deleted file mode 100644
index f45e802..0000000
--- a/controller/tea_poor/test/test_local/unity_config.h
+++ /dev/null
@@ -1,244 +0,0 @@
-/* Unity Configuration
- * As of May 11th, 2016 at ThrowTheSwitch/Unity commit 837c529
- * Update: December 29th, 2016
- * See Also: Unity/docs/UnityConfigurationGuide.pdf
- *
- * Unity is designed to run on almost anything that is targeted by a C compiler.
- * It would be awesome if this could be done with zero configuration. While
- * there are some targets that come close to this dream, it is sadly not
- * universal. It is likely that you are going to need at least a couple of the
- * configuration options described in this document.
- *
- * All of Unity's configuration options are `#defines`. Most of these are simple
- * definitions. A couple are macros with arguments. They live inside the
- * unity_internals.h header file. We don't necessarily recommend opening that
- * file unless you really need to. That file is proof that a cross-platform
- * library is challenging to build. From a more positive perspective, it is also
- * proof that a great deal of complexity can be centralized primarily to one
- * place in order to provide a more consistent and simple experience elsewhere.
- *
- * Using These Options
- * It doesn't matter if you're using a target-specific compiler and a simulator
- * or a native compiler. In either case, you've got a couple choices for
- * configuring these options:
- *
- *  1. Because these options are specified via C defines, you can pass most of
- *     these options to your compiler through command line compiler flags. Even
- *     if you're using an embedded target that forces you to use their
- *     overbearing IDE for all configuration, there will be a place somewhere in
- *     your project to configure defines for your compiler.
- *  2. You can create a custom `unity_config.h` configuration file (present in
- *     your toolchain's search paths). In this file, you will list definitions
- *     and macros specific to your target. All you must do is define
- *     `UNITY_INCLUDE_CONFIG_H` and Unity will rely on `unity_config.h` for any
- *     further definitions it may need.
- */
-
-#ifndef UNITY_CONFIG_H
-#define UNITY_CONFIG_H
-
-/* ************************* AUTOMATIC INTEGER TYPES ***************************
- * C's concept of an integer varies from target to target. The C Standard has
- * rules about the `int` matching the register size of the target
- * microprocessor. It has rules about the `int` and how its size relates to
- * other integer types. An `int` on one target might be 16 bits while on another
- * target it might be 64. There are more specific types in compilers compliant
- * with C99 or later, but that's certainly not every compiler you are likely to
- * encounter. Therefore, Unity has a number of features for helping to adjust
- * itself to match your required integer sizes. It starts off by trying to do it
- * automatically.
- **************************************************************************** */
-
-/* The first attempt to guess your types is to check `limits.h`. Some compilers
- * that don't support `stdint.h` could include `limits.h`. If you don't
- * want Unity to check this file, define this to make it skip the inclusion.
- * Unity looks at UINT_MAX & ULONG_MAX, which were available since C89.
- */
-/* #define UNITY_EXCLUDE_LIMITS_H */
-
-/* The second thing that Unity does to guess your types is check `stdint.h`.
- * This file defines `UINTPTR_MAX`, since C99, that Unity can make use of to
- * learn about your system. It's possible you don't want it to do this or it's
- * possible that your system doesn't support `stdint.h`. If that's the case,
- * you're going to want to define this. That way, Unity will know to skip the
- * inclusion of this file and you won't be left with a compiler error.
- */
-/* #define UNITY_EXCLUDE_STDINT_H */
-
-/* ********************** MANUAL INTEGER TYPE DEFINITION ***********************
- * If you've disabled all of the automatic options above, you're going to have
- * to do the configuration yourself. There are just a handful of defines that
- * you are going to specify if you don't like the defaults.
- **************************************************************************** */
-
- /* Define this to be the number of bits an `int` takes up on your system. The
- * default, if not auto-detected, is 32 bits.
- *
- * Example:
- */
-/* #define UNITY_INT_WIDTH 16 */
-
-/* Define this to be the number of bits a `long` takes up on your system. The
- * default, if not autodetected, is 32 bits. This is used to figure out what
- * kind of 64-bit support your system can handle.  Does it need to specify a
- * `long` or a `long long` to get a 64-bit value. On 16-bit systems, this option
- * is going to be ignored.
- *
- * Example:
- */
-/* #define UNITY_LONG_WIDTH 16 */
-
-/* Define this to be the number of bits a pointer takes up on your system. The
- * default, if not autodetected, is 32-bits. If you're getting ugly compiler
- * warnings about casting from pointers, this is the one to look at.
- *
- * Example:
- */
-/* #define UNITY_POINTER_WIDTH 64 */
-
-/* Unity will automatically include 64-bit support if it auto-detects it, or if
- * your `int`, `long`, or pointer widths are greater than 32-bits. Define this
- * to enable 64-bit support if none of the other options already did it for you.
- * There can be a significant size and speed impact to enabling 64-bit support
- * on small targets, so don't define it if you don't need it.
- */
-/* #define UNITY_INCLUDE_64 */
-
-
-/* *************************** FLOATING POINT TYPES ****************************
- * In the embedded world, it's not uncommon for targets to have no support for
- * floating point operations at all or to have support that is limited to only
- * single precision. We are able to guess integer sizes on the fly because
- * integers are always available in at least one size. Floating point, on the
- * other hand, is sometimes not available at all. Trying to include `float.h` on
- * these platforms would result in an error. This leaves manual configuration as
- * the only option.
- **************************************************************************** */
-
- /* By default, Unity guesses that you will want single precision floating point
-  * support, but not double precision. It's easy to change either of these using
-  * the include and exclude options here. You may include neither, just float,
-  * or both, as suits your needs.
-  */
-/* #define UNITY_EXCLUDE_FLOAT  */
-/* #define UNITY_INCLUDE_DOUBLE */
-/* #define UNITY_EXCLUDE_DOUBLE */
-
-/* For features that are enabled, the following floating point options also
- * become available.
- */
-
-/* Unity aims for as small of a footprint as possible and avoids most standard
- * library calls (some embedded platforms don't have a standard library!).
- * Because of this, its routines for printing integer values are minimalist and
- * hand-coded. To keep Unity universal, though, we eventually chose to develop
- * our own floating point print routines. Still, the display of floating point
- * values during a failure are optional. By default, Unity will print the
- * actual results of floating point assertion failures. So a failed assertion
- * will produce a message like "Expected 4.0 Was 4.25". If you would like less
- * verbose failure messages for floating point assertions, use this option to
- * give a failure message `"Values Not Within Delta"` and trim the binary size.
- */
-/* #define UNITY_EXCLUDE_FLOAT_PRINT */
-
-/* If enabled, Unity assumes you want your `FLOAT` asserts to compare standard C
- * floats. If your compiler supports a specialty floating point type, you can
- * always override this behavior by using this definition.
- *
- * Example:
- */
-/* #define UNITY_FLOAT_TYPE float16_t */
-
-/* If enabled, Unity assumes you want your `DOUBLE` asserts to compare standard
- * C doubles. If you would like to change this, you can specify something else
- * by using this option. For example, defining `UNITY_DOUBLE_TYPE` to `long
- * double` could enable gargantuan floating point types on your 64-bit processor
- * instead of the standard `double`.
- *
- * Example:
- */
-/* #define UNITY_DOUBLE_TYPE long double */
-
-/* If you look up `UNITY_ASSERT_EQUAL_FLOAT` and `UNITY_ASSERT_EQUAL_DOUBLE` as
- * documented in the Unity Assertion Guide, you will learn that they are not
- * really asserting that two values are equal but rather that two values are
- * "close enough" to equal. "Close enough" is controlled by these precision
- * configuration options. If you are working with 32-bit floats and/or 64-bit
- * doubles (the normal on most processors), you should have no need to change
- * these options. They are both set to give you approximately 1 significant bit
- * in either direction. The float precision is 0.00001 while the double is
- * 10^-12. For further details on how this works, see the appendix of the Unity
- * Assertion Guide.
- *
- * Example:
- */
-/* #define UNITY_FLOAT_PRECISION 0.001f  */
-/* #define UNITY_DOUBLE_PRECISION 0.001f */
-
-
-/* *************************** MISCELLANEOUS ***********************************
- * Miscellaneous configuration options for Unity
- **************************************************************************** */
-
-/* Unity uses the stddef.h header included in the C standard library for the
- * "NULL" macro. Define this in order to disable the include of stddef.h. If you
- * do this, you have to make sure to provide your own "NULL" definition.
- */
-/* #define UNITY_EXCLUDE_STDDEF_H */
-
-/* Define this to enable the unity formatted print macro:
- * "TEST_PRINTF"
- */
-/* #define UNITY_INCLUDE_PRINT_FORMATTED */
-
-
-/* *************************** TOOLSET CUSTOMIZATION ***************************
- * In addition to the options listed above, there are a number of other options
- * which will come in handy to customize Unity's behavior for your specific
- * toolchain. It is possible that you may not need to touch any of these but
- * certain platforms, particularly those running in simulators, may need to jump
- * through extra hoops to operate properly. These macros will help in those
- * situations.
- **************************************************************************** */
-
-/* By default, Unity prints its results to `stdout` as it runs. This works
- * perfectly fine in most situations where you are using a native compiler for
- * testing. It works on some simulators as well so long as they have `stdout`
- * routed back to the command line. There are times, however, where the
- * simulator will lack support for dumping results or you will want to route
- * results elsewhere for other reasons. In these cases, you should define the
- * `UNITY_OUTPUT_CHAR` macro. This macro accepts a single character at a time
- * (as an `int`, since this is the parameter type of the standard C `putchar`
- * function most commonly used). You may replace this with whatever function
- * call you like.
- *
- * Example:
- * Say you are forced to run your test suite on an embedded processor with no
- * `stdout` option. You decide to route your test result output to a custom
- * serial `RS232_putc()` function you wrote like thus:
- */
-/* #define UNITY_OUTPUT_CHAR(a)                    RS232_putc(a) */
-/* #define UNITY_OUTPUT_CHAR_HEADER_DECLARATION    RS232_putc(int) */
-/* #define UNITY_OUTPUT_FLUSH()                    RS232_flush() */
-/* #define UNITY_OUTPUT_FLUSH_HEADER_DECLARATION   RS232_flush(void) */
-/* #define UNITY_OUTPUT_START()                    RS232_config(115200,1,8,0) */
-/* #define UNITY_OUTPUT_COMPLETE()                 RS232_close() */
-
-/* Some compilers require a custom attribute to be assigned to pointers, like
- * `near` or `far`. In these cases, you can give Unity a safe default for these
- * by defining this option with the attribute you would like.
- *
- * Example:
- */
-/* #define UNITY_PTR_ATTRIBUTE __attribute__((far)) */
-/* #define UNITY_PTR_ATTRIBUTE near */
-
-/* Print execution time of each test when executed in verbose mode
- *
- * Example:
- *
- * TEST - PASS (10 ms)
- */
-/* #define UNITY_INCLUDE_EXEC_TIME */
-
-#endif /* UNITY_CONFIG_H */
\ No newline at end of file

From 5e0bbcfc763343d26da55035c6ad7b637feaa0b8 Mon Sep 17 00:00:00 2001
From: omonrise <arsenys@live.com>
Date: Fri, 29 Dec 2023 16:44:48 +0100
Subject: [PATCH 08/21] remove unity from libdeps

---
 controller/tea_poor/platformio.ini | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/controller/tea_poor/platformio.ini b/controller/tea_poor/platformio.ini
index e711024..a8fe5b5 100644
--- a/controller/tea_poor/platformio.ini
+++ b/controller/tea_poor/platformio.ini
@@ -15,6 +15,6 @@ framework = arduino
 test_framework = unity
 lib_deps = 
 	lasselukkari/aWOT@^3.5.0
-	throwtheswitch/Unity@^2.5.2
+	; throwtheswitch/Unity@^2.5.2
 test_ignore = local
 

From 3288759c6b5f16ea73c2b572e5465a31670207ef Mon Sep 17 00:00:00 2001
From: GreenWizard <sizeof.2011@gmail.com>
Date: Fri, 29 Dec 2023 22:08:46 +0100
Subject: [PATCH 09/21] fix everything?

---
 .../lib/WaterPump/WaterPumpScheduler.cpp      | 10 ++++-
 .../WaterPumpController.cpp                   |  0
 .../WaterPumpController.h                     |  2 +-
 controller/tea_poor/platformio.ini            | 20 +++++++--
 .../WaterPumpScheduler_test.cpp               | 42 +++++++------------
 5 files changed, 41 insertions(+), 33 deletions(-)
 rename controller/tea_poor/lib/{WaterPump => WaterPumpController}/WaterPumpController.cpp (100%)
 rename controller/tea_poor/lib/{WaterPump => WaterPumpController}/WaterPumpController.h (95%)
 rename controller/tea_poor/test/{test_local => test_native}/WaterPumpScheduler_test.cpp (69%)

diff --git a/controller/tea_poor/lib/WaterPump/WaterPumpScheduler.cpp b/controller/tea_poor/lib/WaterPump/WaterPumpScheduler.cpp
index 21bb9fe..888b4bf 100644
--- a/controller/tea_poor/lib/WaterPump/WaterPumpScheduler.cpp
+++ b/controller/tea_poor/lib/WaterPump/WaterPumpScheduler.cpp
@@ -1,10 +1,16 @@
 #include "WaterPumpScheduler.h"
 
-WaterPumpScheduler::WaterPumpScheduler(IWaterPump* waterPump, unsigned long forceStopIntervalMs) {
+WaterPumpScheduler::WaterPumpScheduler(IWaterPump* waterPump, unsigned long forceStopIntervalMs) :
+  _waterPump(waterPump),
+  _forceStopIntervalMs(forceStopIntervalMs)
+{
 }
 
 WaterPumpScheduler::~WaterPumpScheduler() {
-  delete _waterPump;
+  // TODO: find better way to manage memory
+  //       for now it's not a big deal, because Arduino will never stop
+  //       and tests are manage memory by themselves
+  // delete _waterPump;
 }
 
 void WaterPumpScheduler::setup() {
diff --git a/controller/tea_poor/lib/WaterPump/WaterPumpController.cpp b/controller/tea_poor/lib/WaterPumpController/WaterPumpController.cpp
similarity index 100%
rename from controller/tea_poor/lib/WaterPump/WaterPumpController.cpp
rename to controller/tea_poor/lib/WaterPumpController/WaterPumpController.cpp
diff --git a/controller/tea_poor/lib/WaterPump/WaterPumpController.h b/controller/tea_poor/lib/WaterPumpController/WaterPumpController.h
similarity index 95%
rename from controller/tea_poor/lib/WaterPump/WaterPumpController.h
rename to controller/tea_poor/lib/WaterPumpController/WaterPumpController.h
index 511560c..2e55cac 100644
--- a/controller/tea_poor/lib/WaterPump/WaterPumpController.h
+++ b/controller/tea_poor/lib/WaterPumpController/WaterPumpController.h
@@ -1,6 +1,6 @@
 #ifndef WATERPUMPCONTROLLER_H
 #define WATERPUMPCONTROLLER_H
-#include "IWaterPump.h"
+#include <IWaterPump.h>
 
 class WaterPumpController: public IWaterPump {
 private:
diff --git a/controller/tea_poor/platformio.ini b/controller/tea_poor/platformio.ini
index a8fe5b5..10b8810 100644
--- a/controller/tea_poor/platformio.ini
+++ b/controller/tea_poor/platformio.ini
@@ -12,9 +12,23 @@
 platform = renesas-ra
 board = uno_r4_wifi
 framework = arduino
-test_framework = unity
 lib_deps = 
 	lasselukkari/aWOT@^3.5.0
-	; throwtheswitch/Unity@^2.5.2
-test_ignore = local
+test_ignore =  test_native
 
+[env:native]
+platform = native
+test_build_src = no
+test_framework = googletest
+build_src_filter = +<*> -<test/*>
+lib_ldf_mode = deep
+check_flags = --verbose --enable=all --std=c++11
+build_flags = 
+	-std=c++11
+	-Wall	-Wextra	-Wunused
+	-static -static-libgcc -static-libstdc++
+; ignore libraries that are only for the Arduino
+lib_ignore =
+  RemoteControl
+	WaterPumpController
+test_ignore =  test_uno_r4_wifi
\ No newline at end of file
diff --git a/controller/tea_poor/test/test_local/WaterPumpScheduler_test.cpp b/controller/tea_poor/test/test_native/WaterPumpScheduler_test.cpp
similarity index 69%
rename from controller/tea_poor/test/test_local/WaterPumpScheduler_test.cpp
rename to controller/tea_poor/test/test_native/WaterPumpScheduler_test.cpp
index 3417fe9..8e2cd96 100644
--- a/controller/tea_poor/test/test_local/WaterPumpScheduler_test.cpp
+++ b/controller/tea_poor/test/test_native/WaterPumpScheduler_test.cpp
@@ -1,11 +1,9 @@
 // I wasn't able to run tests at all. Run them locally and confirm that they are working.
 // Its either a local problem or a problem with the configuration of the project.
 // Further goes a sketch of the tests, but I wasn't able to run them.
-#include <unity.h>
+#include <gtest/gtest.h>
 #include <WaterPumpScheduler.h>
 
-
-
 // Fake water pump
 class FakeWaterPump : public IWaterPump {
 private:
@@ -19,14 +17,8 @@ class FakeWaterPump : public IWaterPump {
 };
 // End of fake water pump
 
-
-/* Empty functions required by unity framework*/
-void setUp() { /* Setup code here */ }
-void tearDown() { /* Teardown code here */ }
-
-
 // test that pump is stopping after given time
-void test_pump_stops_after_given_time() {
+TEST(WaterPumpScheduler, test_pump_stops_after_given_time) {
   // random time between 1 and 10 seconds
   const unsigned long runTimeMs = 1000 + (rand() % 10) * 1000;
   FakeWaterPump fakeWaterPump;
@@ -37,21 +29,21 @@ void test_pump_stops_after_given_time() {
   waterPumpScheduler.start(runTimeMs, currentTimeMs);
   // check status
   auto status = waterPumpScheduler.status();
-  TEST_ASSERT_TRUE(status.isRunning);
-  TEST_ASSERT_EQUAL(status.stopTime, runTimeMs);
+  ASSERT_TRUE(status.isRunning);
+  ASSERT_EQ(status.stopTime, runTimeMs);
 
   while (currentTimeMs < runTimeMs) {
     waterPumpScheduler.tick(currentTimeMs);
-    TEST_ASSERT_TRUE(fakeWaterPump.isRunning());
+    ASSERT_TRUE(fakeWaterPump.isRunning());
     currentTimeMs += 100;
   }
   // pump should be stopped after given time
   waterPumpScheduler.tick(runTimeMs + 1);
-  TEST_ASSERT_FALSE(fakeWaterPump.isRunning());
+  ASSERT_FALSE(fakeWaterPump.isRunning());
 }
 
 // test that pump is periodically forced to stop after given time
-void test_pump_is_periodically_forced_to_stop_after_given_time () {
+TEST(WaterPumpScheduler, test_pump_is_periodically_forced_to_stop_after_given_time) {
   FakeWaterPump fakeWaterPump;
   WaterPumpScheduler waterPumpScheduler(&fakeWaterPump, 1000); // force stop each 1 second
   waterPumpScheduler.setup();
@@ -60,25 +52,21 @@ void test_pump_is_periodically_forced_to_stop_after_given_time () {
   waterPumpScheduler.start(1, currentTimeMs);
   currentTimeMs += 1;
   waterPumpScheduler.tick(currentTimeMs);
-  TEST_ASSERT_FALSE(fakeWaterPump.isRunning()); // pump should be stopped after given time
+  ASSERT_FALSE(fakeWaterPump.isRunning()); // pump should be stopped after given time
 
   for(int i = 0; i < 10; i++) {
     // emulate that pump was started again
     fakeWaterPump.start();
     currentTimeMs += 1000;
     waterPumpScheduler.tick(currentTimeMs);
-    TEST_ASSERT_FALSE(fakeWaterPump.isRunning()); // pump should be stopped
+    ASSERT_FALSE(fakeWaterPump.isRunning()); // pump should be stopped
   }
 }
 
-void setup() {
-  UNITY_BEGIN();
-  RUN_TEST(test_pump_stops_after_given_time);
-  RUN_TEST(test_pump_is_periodically_forced_to_stop_after_given_time);
-  UNITY_END();
-}
-
-
-void loop() {
-
+int main(int argc, char **argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  int result = RUN_ALL_TESTS(); // Intentionally ignoring the return value
+  (void)result; // Silence unused variable warning
+  // Always return zero-code and allow PlatformIO to parse results
+  return 0;
 }
\ No newline at end of file

From 7f01633df597588086b6cce83b891b2fef5f86ad Mon Sep 17 00:00:00 2001
From: GreenWizard <sizeof.2011@gmail.com>
Date: Fri, 29 Dec 2023 22:12:59 +0100
Subject: [PATCH 10/21] default env for platformio build

---
 controller/tea_poor/platformio.ini | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/controller/tea_poor/platformio.ini b/controller/tea_poor/platformio.ini
index 10b8810..82d27b9 100644
--- a/controller/tea_poor/platformio.ini
+++ b/controller/tea_poor/platformio.ini
@@ -31,4 +31,7 @@ build_flags =
 lib_ignore =
   RemoteControl
 	WaterPumpController
-test_ignore =  test_uno_r4_wifi
\ No newline at end of file
+test_ignore =  test_uno_r4_wifi
+
+[platformio]
+default_envs = uno_r4_wifi
\ No newline at end of file

From 6dd04631ce8024be5069e9096700bd76b4b87cc3 Mon Sep 17 00:00:00 2001
From: GreenWizard <sizeof.2011@gmail.com>
Date: Fri, 29 Dec 2023 22:21:19 +0100
Subject: [PATCH 11/21] configure CI to build only for Android

---
 .github/workflows/platformio.yaml | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/platformio.yaml b/.github/workflows/platformio.yaml
index 460fb89..90a47b5 100644
--- a/.github/workflows/platformio.yaml
+++ b/.github/workflows/platformio.yaml
@@ -20,6 +20,9 @@ jobs:
       - name: Install PlatformIO Core
         run: pip install --upgrade platformio
 
-      - name: Build PlatformIO Project
+      - name: Run tests on the native platform
+        run: platformio test -e native
+
+      - name: Build PlatformIO Project for Android
         working-directory: ./controller/tea_poor
-        run: pio run
+        run: pio run -e uno_r4_wifi

From 7c12ef04bf26bc0c75309274b86937b76ba0cc05 Mon Sep 17 00:00:00 2001
From: GreenWizard <sizeof.2011@gmail.com>
Date: Fri, 29 Dec 2023 22:26:24 +0100
Subject: [PATCH 12/21] fix

---
 .github/workflows/platformio.yaml | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/platformio.yaml b/.github/workflows/platformio.yaml
index 90a47b5..c24ab99 100644
--- a/.github/workflows/platformio.yaml
+++ b/.github/workflows/platformio.yaml
@@ -21,7 +21,8 @@ jobs:
         run: pip install --upgrade platformio
 
       - name: Run tests on the native platform
-        run: platformio test -e native
+        working-directory: ./controller/tea_poor
+        run: pio test -e native
 
       - name: Build PlatformIO Project for Android
         working-directory: ./controller/tea_poor

From e2f62b68d500e351fb55ab61e1d50c8e7d2165fb Mon Sep 17 00:00:00 2001
From: GreenWizard <sizeof.2011@gmail.com>
Date: Fri, 29 Dec 2023 22:49:41 +0100
Subject: [PATCH 13/21] fix typo

---
 .github/workflows/platformio.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/platformio.yaml b/.github/workflows/platformio.yaml
index c24ab99..95b86ce 100644
--- a/.github/workflows/platformio.yaml
+++ b/.github/workflows/platformio.yaml
@@ -24,6 +24,6 @@ jobs:
         working-directory: ./controller/tea_poor
         run: pio test -e native
 
-      - name: Build PlatformIO Project for Android
+      - name: Build PlatformIO Project for Arduino
         working-directory: ./controller/tea_poor
         run: pio run -e uno_r4_wifi

From 7761f469e8f3c760df8c9c7ef54828e025f71756 Mon Sep 17 00:00:00 2001
From: Pieter Geelen <pieter@geelen.io>
Date: Fri, 29 Dec 2023 23:01:14 +0100
Subject: [PATCH 14/21] Separate out CI

---
 ...{platformio.yaml => platformio_build.yaml} |  6 +----
 .github/workflows/platformio_tests.yaml       | 25 +++++++++++++++++++
 2 files changed, 26 insertions(+), 5 deletions(-)
 rename .github/workflows/{platformio.yaml => platformio_build.yaml} (79%)
 create mode 100644 .github/workflows/platformio_tests.yaml

diff --git a/.github/workflows/platformio.yaml b/.github/workflows/platformio_build.yaml
similarity index 79%
rename from .github/workflows/platformio.yaml
rename to .github/workflows/platformio_build.yaml
index 95b86ce..29ddd58 100644
--- a/.github/workflows/platformio.yaml
+++ b/.github/workflows/platformio_build.yaml
@@ -1,4 +1,4 @@
-name: PlatformIO CI
+name: PlatformIO CI - Build for Arduino
 
 on: [push]
 
@@ -20,10 +20,6 @@ jobs:
       - name: Install PlatformIO Core
         run: pip install --upgrade platformio
 
-      - name: Run tests on the native platform
-        working-directory: ./controller/tea_poor
-        run: pio test -e native
-
       - name: Build PlatformIO Project for Arduino
         working-directory: ./controller/tea_poor
         run: pio run -e uno_r4_wifi
diff --git a/.github/workflows/platformio_tests.yaml b/.github/workflows/platformio_tests.yaml
new file mode 100644
index 0000000..299db5f
--- /dev/null
+++ b/.github/workflows/platformio_tests.yaml
@@ -0,0 +1,25 @@
+name: PlatformIO CI - Unit Tests
+
+on: [push]
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: actions/checkout@v3
+      - uses: actions/cache@v3
+        with:
+          path: |
+            ~/.cache/pip
+            ~/.platformio/.cache
+          key: ${{ runner.os }}-pio
+      - uses: actions/setup-python@v4
+        with:
+          python-version: "3.9"
+      - name: Install PlatformIO Core
+        run: pip install --upgrade platformio
+
+      - name: Run tests on the native platform
+        working-directory: ./controller/tea_poor
+        run: pio test -e native

From 7046cf40d8e3c171ea9575a03f4ece5f024b7034 Mon Sep 17 00:00:00 2001
From: GreenWizard <sizeof.2011@gmail.com>
Date: Sat, 30 Dec 2023 10:16:07 +0100
Subject: [PATCH 15/21] Update user authentication logic

---
 controller/tea_poor/lib/WaterPump/IWaterPump.h |  4 ++++
 .../lib/WaterPump/WaterPumpScheduler.cpp       |  9 ++-------
 .../lib/WaterPump/WaterPumpScheduler.h         |  6 +++---
 controller/tea_poor/src/main.cpp               |  8 ++++----
 .../test_native/WaterPumpScheduler_test.cpp    | 18 +++++++++---------
 5 files changed, 22 insertions(+), 23 deletions(-)

diff --git a/controller/tea_poor/lib/WaterPump/IWaterPump.h b/controller/tea_poor/lib/WaterPump/IWaterPump.h
index 4377980..eef9ee9 100644
--- a/controller/tea_poor/lib/WaterPump/IWaterPump.h
+++ b/controller/tea_poor/lib/WaterPump/IWaterPump.h
@@ -1,6 +1,8 @@
 #ifndef IWATERPUMP_H
 #define IWATERPUMP_H
 
+#include <memory>
+
 class IWaterPump {
 public:
   virtual ~IWaterPump() {}
@@ -12,4 +14,6 @@ class IWaterPump {
   virtual bool isRunning() const = 0;
 };
 
+// define shared pointer alias
+using IWaterPumpPtr = std::shared_ptr<IWaterPump>;
 #endif
\ No newline at end of file
diff --git a/controller/tea_poor/lib/WaterPump/WaterPumpScheduler.cpp b/controller/tea_poor/lib/WaterPump/WaterPumpScheduler.cpp
index 888b4bf..eb9ae02 100644
--- a/controller/tea_poor/lib/WaterPump/WaterPumpScheduler.cpp
+++ b/controller/tea_poor/lib/WaterPump/WaterPumpScheduler.cpp
@@ -1,17 +1,12 @@
 #include "WaterPumpScheduler.h"
 
-WaterPumpScheduler::WaterPumpScheduler(IWaterPump* waterPump, unsigned long forceStopIntervalMs) :
+WaterPumpScheduler::WaterPumpScheduler(IWaterPumpPtr waterPump, unsigned long forceStopIntervalMs) :
   _waterPump(waterPump),
   _forceStopIntervalMs(forceStopIntervalMs)
 {
 }
 
-WaterPumpScheduler::~WaterPumpScheduler() {
-  // TODO: find better way to manage memory
-  //       for now it's not a big deal, because Arduino will never stop
-  //       and tests are manage memory by themselves
-  // delete _waterPump;
-}
+WaterPumpScheduler::~WaterPumpScheduler() {}
 
 void WaterPumpScheduler::setup() {
   _waterPump->setup();
diff --git a/controller/tea_poor/lib/WaterPump/WaterPumpScheduler.h b/controller/tea_poor/lib/WaterPump/WaterPumpScheduler.h
index 6abb21f..84a6145 100644
--- a/controller/tea_poor/lib/WaterPump/WaterPumpScheduler.h
+++ b/controller/tea_poor/lib/WaterPump/WaterPumpScheduler.h
@@ -8,13 +8,13 @@
 // It is also ensuring that water pump is stopped if not needed
 class WaterPumpScheduler {
 private:
-  IWaterPump* _waterPump;
+  IWaterPumpPtr _waterPump;
   unsigned long _stopTime = 0;
   // each X milliseconds will force stop water pump
   unsigned long _forceStopIntervalMs;
 public:
-  WaterPumpScheduler(IWaterPump* waterPump, unsigned long forceStopIntervalMs);
-  WaterPumpScheduler(IWaterPump* waterPump) : WaterPumpScheduler(waterPump, 1000) {}
+  WaterPumpScheduler(IWaterPumpPtr waterPump, unsigned long forceStopIntervalMs);
+  WaterPumpScheduler(IWaterPumpPtr waterPump) : WaterPumpScheduler(waterPump, 1000) {}
   ~WaterPumpScheduler();
 
   void setup();
diff --git a/controller/tea_poor/src/main.cpp b/controller/tea_poor/src/main.cpp
index 82747dd..0a25e02 100644
--- a/controller/tea_poor/src/main.cpp
+++ b/controller/tea_poor/src/main.cpp
@@ -1,11 +1,12 @@
 #include <Arduino.h>
+#include <memory>
 #include <WaterPumpController.h>
 #include <WaterPumpScheduler.h>
 #include <RemoteControl.h>
 
 // Setting up water pump
 WaterPumpScheduler waterPump(
-  new WaterPumpController(12, 9, 3)
+  std::make_shared<WaterPumpController>(12, 9, 3)
 );
 // Just for safety reasons, we don't want to pour tea for too long
 // Their is no reason to make it configurable and add unnecessary complexity
@@ -59,9 +60,8 @@ void pour_tea(Request &req, Response &res) {
   char milliseconds[64];
   req.query("milliseconds", milliseconds, 64);
   if (!isValidIntNumber(milliseconds, WATER_PUMP_SAFE_THRESHOLD)) {
-    res.println("Please specify amount of milliseconds in query parameter; pour_tea?milliseconds=10 e.g.");
-    res.print("Maximal allowed time is: ");
-    res.println(WATER_PUMP_SAFE_THRESHOLD);
+    // send error message as JSON
+    res.println("{ \"error\": \"invalid milliseconds value\" }");
     return;
   }
   // start pouring tea
diff --git a/controller/tea_poor/test/test_native/WaterPumpScheduler_test.cpp b/controller/tea_poor/test/test_native/WaterPumpScheduler_test.cpp
index 8e2cd96..2b3b40f 100644
--- a/controller/tea_poor/test/test_native/WaterPumpScheduler_test.cpp
+++ b/controller/tea_poor/test/test_native/WaterPumpScheduler_test.cpp
@@ -21,8 +21,8 @@ class FakeWaterPump : public IWaterPump {
 TEST(WaterPumpScheduler, test_pump_stops_after_given_time) {
   // random time between 1 and 10 seconds
   const unsigned long runTimeMs = 1000 + (rand() % 10) * 1000;
-  FakeWaterPump fakeWaterPump;
-  WaterPumpScheduler waterPumpScheduler(&fakeWaterPump);
+  IWaterPumpPtr fakeWaterPump = std::make_shared<FakeWaterPump>();
+  WaterPumpScheduler waterPumpScheduler(fakeWaterPump);
   waterPumpScheduler.setup();
   // start water pump
   unsigned long currentTimeMs = 0;
@@ -34,32 +34,32 @@ TEST(WaterPumpScheduler, test_pump_stops_after_given_time) {
 
   while (currentTimeMs < runTimeMs) {
     waterPumpScheduler.tick(currentTimeMs);
-    ASSERT_TRUE(fakeWaterPump.isRunning());
+    ASSERT_TRUE(fakeWaterPump->isRunning());
     currentTimeMs += 100;
   }
   // pump should be stopped after given time
   waterPumpScheduler.tick(runTimeMs + 1);
-  ASSERT_FALSE(fakeWaterPump.isRunning());
+  ASSERT_FALSE(fakeWaterPump->isRunning());
 }
 
 // test that pump is periodically forced to stop after given time
 TEST(WaterPumpScheduler, test_pump_is_periodically_forced_to_stop_after_given_time) {
-  FakeWaterPump fakeWaterPump;
-  WaterPumpScheduler waterPumpScheduler(&fakeWaterPump, 1000); // force stop each 1 second
+  IWaterPumpPtr fakeWaterPump = std::make_shared<FakeWaterPump>();
+  WaterPumpScheduler waterPumpScheduler(fakeWaterPump, 1000); // force stop each 1 second
   waterPumpScheduler.setup();
   // start water pump
   unsigned long currentTimeMs = 0;
   waterPumpScheduler.start(1, currentTimeMs);
   currentTimeMs += 1;
   waterPumpScheduler.tick(currentTimeMs);
-  ASSERT_FALSE(fakeWaterPump.isRunning()); // pump should be stopped after given time
+  ASSERT_FALSE(fakeWaterPump->isRunning()); // pump should be stopped after given time
 
   for(int i = 0; i < 10; i++) {
     // emulate that pump was started again
-    fakeWaterPump.start();
+    fakeWaterPump->start();
     currentTimeMs += 1000;
     waterPumpScheduler.tick(currentTimeMs);
-    ASSERT_FALSE(fakeWaterPump.isRunning()); // pump should be stopped
+    ASSERT_FALSE(fakeWaterPump->isRunning()); // pump should be stopped
   }
 }
 

From 89347f0e5ea88ebe97624073d5dee445a162c77e Mon Sep 17 00:00:00 2001
From: GreenWizard <sizeof.2011@gmail.com>
Date: Sat, 30 Dec 2023 10:16:07 +0100
Subject: [PATCH 16/21] shared_ptr + return error as JSON

---
 controller/tea_poor/lib/WaterPump/IWaterPump.h |  4 ++++
 .../lib/WaterPump/WaterPumpScheduler.cpp       |  9 ++-------
 .../lib/WaterPump/WaterPumpScheduler.h         |  6 +++---
 controller/tea_poor/src/main.cpp               |  8 ++++----
 .../test_native/WaterPumpScheduler_test.cpp    | 18 +++++++++---------
 5 files changed, 22 insertions(+), 23 deletions(-)

diff --git a/controller/tea_poor/lib/WaterPump/IWaterPump.h b/controller/tea_poor/lib/WaterPump/IWaterPump.h
index 4377980..eef9ee9 100644
--- a/controller/tea_poor/lib/WaterPump/IWaterPump.h
+++ b/controller/tea_poor/lib/WaterPump/IWaterPump.h
@@ -1,6 +1,8 @@
 #ifndef IWATERPUMP_H
 #define IWATERPUMP_H
 
+#include <memory>
+
 class IWaterPump {
 public:
   virtual ~IWaterPump() {}
@@ -12,4 +14,6 @@ class IWaterPump {
   virtual bool isRunning() const = 0;
 };
 
+// define shared pointer alias
+using IWaterPumpPtr = std::shared_ptr<IWaterPump>;
 #endif
\ No newline at end of file
diff --git a/controller/tea_poor/lib/WaterPump/WaterPumpScheduler.cpp b/controller/tea_poor/lib/WaterPump/WaterPumpScheduler.cpp
index 888b4bf..eb9ae02 100644
--- a/controller/tea_poor/lib/WaterPump/WaterPumpScheduler.cpp
+++ b/controller/tea_poor/lib/WaterPump/WaterPumpScheduler.cpp
@@ -1,17 +1,12 @@
 #include "WaterPumpScheduler.h"
 
-WaterPumpScheduler::WaterPumpScheduler(IWaterPump* waterPump, unsigned long forceStopIntervalMs) :
+WaterPumpScheduler::WaterPumpScheduler(IWaterPumpPtr waterPump, unsigned long forceStopIntervalMs) :
   _waterPump(waterPump),
   _forceStopIntervalMs(forceStopIntervalMs)
 {
 }
 
-WaterPumpScheduler::~WaterPumpScheduler() {
-  // TODO: find better way to manage memory
-  //       for now it's not a big deal, because Arduino will never stop
-  //       and tests are manage memory by themselves
-  // delete _waterPump;
-}
+WaterPumpScheduler::~WaterPumpScheduler() {}
 
 void WaterPumpScheduler::setup() {
   _waterPump->setup();
diff --git a/controller/tea_poor/lib/WaterPump/WaterPumpScheduler.h b/controller/tea_poor/lib/WaterPump/WaterPumpScheduler.h
index 6abb21f..84a6145 100644
--- a/controller/tea_poor/lib/WaterPump/WaterPumpScheduler.h
+++ b/controller/tea_poor/lib/WaterPump/WaterPumpScheduler.h
@@ -8,13 +8,13 @@
 // It is also ensuring that water pump is stopped if not needed
 class WaterPumpScheduler {
 private:
-  IWaterPump* _waterPump;
+  IWaterPumpPtr _waterPump;
   unsigned long _stopTime = 0;
   // each X milliseconds will force stop water pump
   unsigned long _forceStopIntervalMs;
 public:
-  WaterPumpScheduler(IWaterPump* waterPump, unsigned long forceStopIntervalMs);
-  WaterPumpScheduler(IWaterPump* waterPump) : WaterPumpScheduler(waterPump, 1000) {}
+  WaterPumpScheduler(IWaterPumpPtr waterPump, unsigned long forceStopIntervalMs);
+  WaterPumpScheduler(IWaterPumpPtr waterPump) : WaterPumpScheduler(waterPump, 1000) {}
   ~WaterPumpScheduler();
 
   void setup();
diff --git a/controller/tea_poor/src/main.cpp b/controller/tea_poor/src/main.cpp
index 82747dd..0a25e02 100644
--- a/controller/tea_poor/src/main.cpp
+++ b/controller/tea_poor/src/main.cpp
@@ -1,11 +1,12 @@
 #include <Arduino.h>
+#include <memory>
 #include <WaterPumpController.h>
 #include <WaterPumpScheduler.h>
 #include <RemoteControl.h>
 
 // Setting up water pump
 WaterPumpScheduler waterPump(
-  new WaterPumpController(12, 9, 3)
+  std::make_shared<WaterPumpController>(12, 9, 3)
 );
 // Just for safety reasons, we don't want to pour tea for too long
 // Their is no reason to make it configurable and add unnecessary complexity
@@ -59,9 +60,8 @@ void pour_tea(Request &req, Response &res) {
   char milliseconds[64];
   req.query("milliseconds", milliseconds, 64);
   if (!isValidIntNumber(milliseconds, WATER_PUMP_SAFE_THRESHOLD)) {
-    res.println("Please specify amount of milliseconds in query parameter; pour_tea?milliseconds=10 e.g.");
-    res.print("Maximal allowed time is: ");
-    res.println(WATER_PUMP_SAFE_THRESHOLD);
+    // send error message as JSON
+    res.println("{ \"error\": \"invalid milliseconds value\" }");
     return;
   }
   // start pouring tea
diff --git a/controller/tea_poor/test/test_native/WaterPumpScheduler_test.cpp b/controller/tea_poor/test/test_native/WaterPumpScheduler_test.cpp
index 8e2cd96..2b3b40f 100644
--- a/controller/tea_poor/test/test_native/WaterPumpScheduler_test.cpp
+++ b/controller/tea_poor/test/test_native/WaterPumpScheduler_test.cpp
@@ -21,8 +21,8 @@ class FakeWaterPump : public IWaterPump {
 TEST(WaterPumpScheduler, test_pump_stops_after_given_time) {
   // random time between 1 and 10 seconds
   const unsigned long runTimeMs = 1000 + (rand() % 10) * 1000;
-  FakeWaterPump fakeWaterPump;
-  WaterPumpScheduler waterPumpScheduler(&fakeWaterPump);
+  IWaterPumpPtr fakeWaterPump = std::make_shared<FakeWaterPump>();
+  WaterPumpScheduler waterPumpScheduler(fakeWaterPump);
   waterPumpScheduler.setup();
   // start water pump
   unsigned long currentTimeMs = 0;
@@ -34,32 +34,32 @@ TEST(WaterPumpScheduler, test_pump_stops_after_given_time) {
 
   while (currentTimeMs < runTimeMs) {
     waterPumpScheduler.tick(currentTimeMs);
-    ASSERT_TRUE(fakeWaterPump.isRunning());
+    ASSERT_TRUE(fakeWaterPump->isRunning());
     currentTimeMs += 100;
   }
   // pump should be stopped after given time
   waterPumpScheduler.tick(runTimeMs + 1);
-  ASSERT_FALSE(fakeWaterPump.isRunning());
+  ASSERT_FALSE(fakeWaterPump->isRunning());
 }
 
 // test that pump is periodically forced to stop after given time
 TEST(WaterPumpScheduler, test_pump_is_periodically_forced_to_stop_after_given_time) {
-  FakeWaterPump fakeWaterPump;
-  WaterPumpScheduler waterPumpScheduler(&fakeWaterPump, 1000); // force stop each 1 second
+  IWaterPumpPtr fakeWaterPump = std::make_shared<FakeWaterPump>();
+  WaterPumpScheduler waterPumpScheduler(fakeWaterPump, 1000); // force stop each 1 second
   waterPumpScheduler.setup();
   // start water pump
   unsigned long currentTimeMs = 0;
   waterPumpScheduler.start(1, currentTimeMs);
   currentTimeMs += 1;
   waterPumpScheduler.tick(currentTimeMs);
-  ASSERT_FALSE(fakeWaterPump.isRunning()); // pump should be stopped after given time
+  ASSERT_FALSE(fakeWaterPump->isRunning()); // pump should be stopped after given time
 
   for(int i = 0; i < 10; i++) {
     // emulate that pump was started again
-    fakeWaterPump.start();
+    fakeWaterPump->start();
     currentTimeMs += 1000;
     waterPumpScheduler.tick(currentTimeMs);
-    ASSERT_FALSE(fakeWaterPump.isRunning()); // pump should be stopped
+    ASSERT_FALSE(fakeWaterPump->isRunning()); // pump should be stopped
   }
 }
 

From 63bee21db17aaf5c7e07bb45ee765dd09a20947b Mon Sep 17 00:00:00 2001
From: GreenWizard <sizeof.2011@gmail.com>
Date: Sat, 30 Dec 2023 12:50:17 +0100
Subject: [PATCH 17/21] small rework

---
 controller/tea_poor/src/main.cpp | 52 +++++++++++++++++---------------
 1 file changed, 28 insertions(+), 24 deletions(-)

diff --git a/controller/tea_poor/src/main.cpp b/controller/tea_poor/src/main.cpp
index 0a25e02..adfa85b 100644
--- a/controller/tea_poor/src/main.cpp
+++ b/controller/tea_poor/src/main.cpp
@@ -4,6 +4,7 @@
 #include <WaterPumpScheduler.h>
 #include <RemoteControl.h>
 
+#include <sstream>
 // Setting up water pump
 WaterPumpScheduler waterPump(
   std::make_shared<WaterPumpController>(12, 9, 3)
@@ -18,34 +19,27 @@ RemoteControl remoteControl(
   "VerySecurePassword" // network password
 );
 
-void _sendSystemStatus(Response &res) {
-  // send system status as JSON
-  res.println("{");
+void _sendSystemStatus(std::ostream& response) {
+  response << "{";
   // send water threshold
-  res.print("\"water threshold\": ");
-  res.print(WATER_PUMP_SAFE_THRESHOLD);
-  res.println(",");
-  
+  response << "\"water threshold\": " << WATER_PUMP_SAFE_THRESHOLD << ",";
   // send water pump status
   const auto waterPumpStatus = waterPump.status();
-  res.println("\"pump\": {");
-  res.print("\"running\": ");
-  res.print(waterPumpStatus.isRunning ? "true, " : "false, ");
   const unsigned long timeLeft = 
     waterPumpStatus.isRunning ?
     waterPumpStatus.stopTime - millis() :
     0;
-  res.print("\"time left\": ");
-  res.print(timeLeft);
-  res.println("},");
+  response 
+    << "\"pump\": {"
+    << "  \"running\": " << (waterPumpStatus.isRunning ? "true, " : "false, ")
+    << "  \"time left\": " << timeLeft
+    << "}";
   // end of water pump status
   ///////////////////////////////////
   // send remote control status
-  res.print("\"remote control\": ");
-  res.print(remoteControl.asJSONString());
-  res.println();
+  response << "\"remote control\": " << remoteControl.asJSONString();
   // end of JSON
-  res.println("}");
+  response << "}";
 }
 
 bool isValidIntNumber(const char *str, const int maxValue, const int minValue=0) {
@@ -56,12 +50,10 @@ bool isValidIntNumber(const char *str, const int maxValue, const int minValue=0)
   return true;
 }
 
-void pour_tea(Request &req, Response &res) {
-  char milliseconds[64];
-  req.query("milliseconds", milliseconds, 64);
+void pour_tea(const char *milliseconds, std::ostream &res) {
   if (!isValidIntNumber(milliseconds, WATER_PUMP_SAFE_THRESHOLD)) {
     // send error message as JSON
-    res.println("{ \"error\": \"invalid milliseconds value\" }");
+    res << "{ \"error\": \"invalid milliseconds value\" }";
     return;
   }
   // start pouring tea
@@ -72,16 +64,28 @@ void pour_tea(Request &req, Response &res) {
 void setup() {
   Serial.begin(9600);
   waterPump.setup();
+  // TODO: find a way to remove redundant code with string streams
   remoteControl.setup([](Application &app) {
-    app.get("/pour_tea", pour_tea);
+    app.get("/pour_tea", [](Request &req, Response &res) {
+      char milliseconds[64];
+      req.query("milliseconds", milliseconds, 64);
+      
+      std::stringstream response;
+      pour_tea(milliseconds, response);
+      res.println(response.str().c_str());
+    });
     // stop water pump
     app.get("/stop", [](Request &req, Response &res) {
       waterPump.stop();
-      _sendSystemStatus(res);
+      std::stringstream response;
+      _sendSystemStatus(response);
+      res.println(response.str().c_str());
     });
     // get system status
     app.get("/status", [](Request &req, Response &res) {
-      _sendSystemStatus(res);
+      std::stringstream response;
+      _sendSystemStatus(response);
+      res.println(response.str().c_str());
     });
   });
 }

From 311e31468aa251faf572ab696d15e7d2cabdbf6f Mon Sep 17 00:00:00 2001
From: GreenWizard <sizeof.2011@gmail.com>
Date: Sat, 30 Dec 2023 13:45:24 +0100
Subject: [PATCH 18/21] extracted secrets from main.cpp

---
 controller/tea_poor/.gitignore            |  3 +++
 controller/tea_poor/src/main.cpp          | 10 +++++-----
 controller/tea_poor/src/secrets.h.example | 14 ++++++++++++++
 3 files changed, 22 insertions(+), 5 deletions(-)
 create mode 100644 controller/tea_poor/src/secrets.h.example

diff --git a/controller/tea_poor/.gitignore b/controller/tea_poor/.gitignore
index 89cc49c..1f7aa88 100644
--- a/controller/tea_poor/.gitignore
+++ b/controller/tea_poor/.gitignore
@@ -3,3 +3,6 @@
 .vscode/c_cpp_properties.json
 .vscode/launch.json
 .vscode/ipch
+
+# hide secrets
+src/secrets.h
\ No newline at end of file
diff --git a/controller/tea_poor/src/main.cpp b/controller/tea_poor/src/main.cpp
index adfa85b..b439390 100644
--- a/controller/tea_poor/src/main.cpp
+++ b/controller/tea_poor/src/main.cpp
@@ -3,21 +3,21 @@
 #include <WaterPumpController.h>
 #include <WaterPumpScheduler.h>
 #include <RemoteControl.h>
+#include "secrets.h"
 
 #include <sstream>
 // Setting up water pump
 WaterPumpScheduler waterPump(
-  std::make_shared<WaterPumpController>(12, 9, 3)
+  std::make_shared<WaterPumpController>(
+    WATER_PUMP_DIRECTION_PIN, WATER_PUMP_BRAKE_PIN, WATER_PUMP_POWER_PIN
+  )
 );
 // Just for safety reasons, we don't want to pour tea for too long
 // Their is no reason to make it configurable and add unnecessary complexity
 const int WATER_PUMP_SAFE_THRESHOLD = 10 * 1000;
 
 // setting up remote control
-RemoteControl remoteControl(
-  "MyWiFiNetwork", // network name/SSID
-  "VerySecurePassword" // network password
-);
+RemoteControl remoteControl(WIFI_SSID, WIFI_PASSWORD);
 
 void _sendSystemStatus(std::ostream& response) {
   response << "{";
diff --git a/controller/tea_poor/src/secrets.h.example b/controller/tea_poor/src/secrets.h.example
new file mode 100644
index 0000000..84d12c9
--- /dev/null
+++ b/controller/tea_poor/src/secrets.h.example
@@ -0,0 +1,14 @@
+// contains user specific information that should not be shared
+#ifndef SECRETS_H
+#define SECRETS_H
+
+// WiFi network name/SSID
+const char* WIFI_SSID = "MyWiFiNetwork";
+const char* WIFI_PASSWORD = "VerySecurePassword";
+
+// PINs for water pump controller
+const int WATER_PUMP_DIRECTION_PIN = 12;
+const int WATER_PUMP_BRAKE_PIN = 9;
+const int WATER_PUMP_POWER_PIN = 3;
+
+#endif // SECRETS_H
\ No newline at end of file

From 75a76291969264cb38f910e8b81aa7430e222c46 Mon Sep 17 00:00:00 2001
From: GreenWizard <sizeof.2011@gmail.com>
Date: Sat, 30 Dec 2023 13:51:37 +0100
Subject: [PATCH 19/21] fix CI

---
 .github/workflows/platformio_build.yaml | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/.github/workflows/platformio_build.yaml b/.github/workflows/platformio_build.yaml
index 29ddd58..0786ae9 100644
--- a/.github/workflows/platformio_build.yaml
+++ b/.github/workflows/platformio_build.yaml
@@ -20,6 +20,10 @@ jobs:
       - name: Install PlatformIO Core
         run: pip install --upgrade platformio
 
+      # rename src/secrets.h.example to src/secrets.h, to use a dummy values during build
+      - name: Copy secrets.h
+        run: cp ./controller/tea_poor/src/secrets.h.example ./controller/tea_poor/src/secrets.h
+
       - name: Build PlatformIO Project for Arduino
         working-directory: ./controller/tea_poor
         run: pio run -e uno_r4_wifi

From 267322c9ea9e7c5e4a7fe03da641ac7bcbf57156 Mon Sep 17 00:00:00 2001
From: GreenWizard <sizeof.2011@gmail.com>
Date: Sat, 30 Dec 2023 22:37:28 +0100
Subject: [PATCH 20/21] very big update. Reorganized the code, added a command
 processor, tests.

---
 .../tea_poor/lib/Arduino/ArduinoEnvironment.h | 15 ++++
 .../RemoteControl.cpp                         |  0
 .../RemoteControl.h                           |  0
 .../WaterPumpController.cpp                   |  0
 .../WaterPumpController.h                     |  0
 .../lib/CommandProcessor/CommandProcessor.cpp | 58 ++++++++++++++
 .../lib/CommandProcessor/CommandProcessor.h   | 30 +++++++
 .../WaterPumpScheduler.cpp                    |  7 +-
 .../WaterPumpScheduler.h                      | 17 ++--
 .../tea_poor/lib/interfaces/IEnvironment.h    | 13 +++
 .../{WaterPump => interfaces}/IWaterPump.h    |  0
 .../lib/interfaces/IWaterPumpSchedulerAPI.h   | 30 +++++++
 controller/tea_poor/platformio.ini            |  4 +-
 controller/tea_poor/src/main.cpp              | 78 +++++-------------
 controller/tea_poor/src/secrets.h.example     |  4 +
 controller/tea_poor/test/test_native/main.cpp | 13 +++
 .../test_native/tests/CommandProcessor_test.h | 79 +++++++++++++++++++
 .../WaterPumpScheduler_test.h}                | 25 +-----
 .../test_native/tests/mocks/FakeEnvironment.h | 19 +++++
 .../test_native/tests/mocks/FakeWaterPump.h   | 18 +++++
 .../tests/mocks/FakeWaterPumpSchedulerAPI.h   | 26 ++++++
 21 files changed, 336 insertions(+), 100 deletions(-)
 create mode 100644 controller/tea_poor/lib/Arduino/ArduinoEnvironment.h
 rename controller/tea_poor/lib/{RemoteControl => Arduino}/RemoteControl.cpp (100%)
 rename controller/tea_poor/lib/{RemoteControl => Arduino}/RemoteControl.h (100%)
 rename controller/tea_poor/lib/{WaterPumpController => Arduino}/WaterPumpController.cpp (100%)
 rename controller/tea_poor/lib/{WaterPumpController => Arduino}/WaterPumpController.h (100%)
 create mode 100644 controller/tea_poor/lib/CommandProcessor/CommandProcessor.cpp
 create mode 100644 controller/tea_poor/lib/CommandProcessor/CommandProcessor.h
 rename controller/tea_poor/lib/{WaterPump => WaterPumpScheduler}/WaterPumpScheduler.cpp (86%)
 rename controller/tea_poor/lib/{WaterPump => WaterPumpScheduler}/WaterPumpScheduler.h (79%)
 create mode 100644 controller/tea_poor/lib/interfaces/IEnvironment.h
 rename controller/tea_poor/lib/{WaterPump => interfaces}/IWaterPump.h (100%)
 create mode 100644 controller/tea_poor/lib/interfaces/IWaterPumpSchedulerAPI.h
 create mode 100644 controller/tea_poor/test/test_native/main.cpp
 create mode 100644 controller/tea_poor/test/test_native/tests/CommandProcessor_test.h
 rename controller/tea_poor/test/test_native/{WaterPumpScheduler_test.cpp => tests/WaterPumpScheduler_test.h} (67%)
 create mode 100644 controller/tea_poor/test/test_native/tests/mocks/FakeEnvironment.h
 create mode 100644 controller/tea_poor/test/test_native/tests/mocks/FakeWaterPump.h
 create mode 100644 controller/tea_poor/test/test_native/tests/mocks/FakeWaterPumpSchedulerAPI.h

diff --git a/controller/tea_poor/lib/Arduino/ArduinoEnvironment.h b/controller/tea_poor/lib/Arduino/ArduinoEnvironment.h
new file mode 100644
index 0000000..bbb5a01
--- /dev/null
+++ b/controller/tea_poor/lib/Arduino/ArduinoEnvironment.h
@@ -0,0 +1,15 @@
+// Arduino environment
+#ifndef ARDUINO_ENVIRONMENT_H
+#define ARDUINO_ENVIRONMENT_H
+
+#include <IEnvironment.h>
+#include <Arduino.h>
+
+class ArduinoEnvironment : public IEnvironment {
+  public:
+    unsigned long time() const override {
+      return millis();
+    }
+};
+
+#endif // ARDUINO_ENVIRONMENT_H
\ No newline at end of file
diff --git a/controller/tea_poor/lib/RemoteControl/RemoteControl.cpp b/controller/tea_poor/lib/Arduino/RemoteControl.cpp
similarity index 100%
rename from controller/tea_poor/lib/RemoteControl/RemoteControl.cpp
rename to controller/tea_poor/lib/Arduino/RemoteControl.cpp
diff --git a/controller/tea_poor/lib/RemoteControl/RemoteControl.h b/controller/tea_poor/lib/Arduino/RemoteControl.h
similarity index 100%
rename from controller/tea_poor/lib/RemoteControl/RemoteControl.h
rename to controller/tea_poor/lib/Arduino/RemoteControl.h
diff --git a/controller/tea_poor/lib/WaterPumpController/WaterPumpController.cpp b/controller/tea_poor/lib/Arduino/WaterPumpController.cpp
similarity index 100%
rename from controller/tea_poor/lib/WaterPumpController/WaterPumpController.cpp
rename to controller/tea_poor/lib/Arduino/WaterPumpController.cpp
diff --git a/controller/tea_poor/lib/WaterPumpController/WaterPumpController.h b/controller/tea_poor/lib/Arduino/WaterPumpController.h
similarity index 100%
rename from controller/tea_poor/lib/WaterPumpController/WaterPumpController.h
rename to controller/tea_poor/lib/Arduino/WaterPumpController.h
diff --git a/controller/tea_poor/lib/CommandProcessor/CommandProcessor.cpp b/controller/tea_poor/lib/CommandProcessor/CommandProcessor.cpp
new file mode 100644
index 0000000..c776abf
--- /dev/null
+++ b/controller/tea_poor/lib/CommandProcessor/CommandProcessor.cpp
@@ -0,0 +1,58 @@
+#include "CommandProcessor.h"
+#include <cstring>
+#include <sstream>
+
+bool isValidIntNumber(const char *str, const int maxValue, const int minValue=0) {
+  const int len = strlen(str);
+  if (len < 1) return false;
+  // check that string contains only digits
+  // first character can be '-' for negative numbers
+  if ((str[0] != '-') && !isdigit(str[0])) return false;
+  for (int i = 1; i < len; i++) {
+    if (!isdigit(str[i])) return false;
+  }
+  // check that number is in range
+  const int value = atoi(str);
+  if (value < minValue) return false;
+  if (maxValue <= value) return false;
+  return true;
+}
+
+std::string CommandProcessor::status() {
+  std::stringstream response;
+  response << "{";
+  // send water threshold
+  response << "\"water threshold\": " << _waterPumpSafeThreshold << ", ";
+  // send water pump status
+  const auto waterPumpStatus = _waterPump->status();
+  const auto now = _env->time();
+  const auto timeLeft = waterPumpStatus.isRunning ? waterPumpStatus.stopTime - now : 0;
+  response 
+    << "\"pump\": {"
+    << "  \"running\": " << (waterPumpStatus.isRunning ? "true, " : "false, ")
+    << "  \"time left\": " << timeLeft
+    << "}";
+  // end of water pump status
+  ///////////////////////////////////
+  // send remote control status
+  // response << "\"remote control\": " << remoteControl.asJSONString();
+  // end of JSON
+  response << "}";
+
+  return response.str();
+}
+
+std::string CommandProcessor::pour_tea(const char *milliseconds) {
+  if (!isValidIntNumber(milliseconds, _waterPumpSafeThreshold)) {
+    // send error message as JSON
+    return std::string("{ \"error\": \"invalid milliseconds value\" }");
+  }
+  // start pouring tea
+  _waterPump->start( atoi(milliseconds), _env->time() );
+  return status();
+}
+
+std::string CommandProcessor::stop() {
+  _waterPump->stop();
+  return status();
+}
\ No newline at end of file
diff --git a/controller/tea_poor/lib/CommandProcessor/CommandProcessor.h b/controller/tea_poor/lib/CommandProcessor/CommandProcessor.h
new file mode 100644
index 0000000..a7770a0
--- /dev/null
+++ b/controller/tea_poor/lib/CommandProcessor/CommandProcessor.h
@@ -0,0 +1,30 @@
+// CommandProcessor class definition
+#ifndef COMMANDPROCESSOR_H
+#define COMMANDPROCESSOR_H
+
+#include <string>
+#include <IWaterPumpSchedulerAPI.h>
+#include <IEnvironment.h>
+
+// This class is used to process incoming commands
+class CommandProcessor {
+  public:
+    CommandProcessor(
+      int waterPumpSafeThreshold,
+      const IEnvironmentPtr env,
+      const IWaterPumpSchedulerAPIPtr waterPump
+    ) : 
+      _waterPumpSafeThreshold(waterPumpSafeThreshold),
+      _env(env),
+      _waterPump(waterPump)
+    {}
+
+    std::string status();
+    std::string pour_tea(const char *milliseconds);
+    std::string stop();
+  private:
+    const int _waterPumpSafeThreshold;
+    const IEnvironmentPtr _env;
+    const IWaterPumpSchedulerAPIPtr _waterPump;
+};
+#endif // COMMANDPROCESSOR_H
\ No newline at end of file
diff --git a/controller/tea_poor/lib/WaterPump/WaterPumpScheduler.cpp b/controller/tea_poor/lib/WaterPumpScheduler/WaterPumpScheduler.cpp
similarity index 86%
rename from controller/tea_poor/lib/WaterPump/WaterPumpScheduler.cpp
rename to controller/tea_poor/lib/WaterPumpScheduler/WaterPumpScheduler.cpp
index eb9ae02..edf3634 100644
--- a/controller/tea_poor/lib/WaterPump/WaterPumpScheduler.cpp
+++ b/controller/tea_poor/lib/WaterPumpScheduler/WaterPumpScheduler.cpp
@@ -29,9 +29,6 @@ void WaterPumpScheduler::tick(unsigned long currentTimeMs) {
   }
 }
 
-WaterPumpScheduler::WaterPumpStatus WaterPumpScheduler::status() {
-  return {
-    _waterPump->isRunning(),
-    _stopTime
-  };
+WaterPumpStatus WaterPumpScheduler::status() {
+  return WaterPumpStatus(_waterPump->isRunning(), _stopTime);
 }
\ No newline at end of file
diff --git a/controller/tea_poor/lib/WaterPump/WaterPumpScheduler.h b/controller/tea_poor/lib/WaterPumpScheduler/WaterPumpScheduler.h
similarity index 79%
rename from controller/tea_poor/lib/WaterPump/WaterPumpScheduler.h
rename to controller/tea_poor/lib/WaterPumpScheduler/WaterPumpScheduler.h
index 84a6145..4e8aa66 100644
--- a/controller/tea_poor/lib/WaterPump/WaterPumpScheduler.h
+++ b/controller/tea_poor/lib/WaterPumpScheduler/WaterPumpScheduler.h
@@ -1,12 +1,13 @@
 #ifndef WATERPUMPSCHEDULER_H
 #define WATERPUMPSCHEDULER_H
 
-#include "IWaterPump.h"
+#include <IWaterPump.h>
+#include <IWaterPumpSchedulerAPI.h>
 
 // This class is responsible for scheduling water pump
 // It is used to make sure that water pump is running for a limited time
 // It is also ensuring that water pump is stopped if not needed
-class WaterPumpScheduler {
+class WaterPumpScheduler : public IWaterPumpSchedulerAPI {
 private:
   IWaterPumpPtr _waterPump;
   unsigned long _stopTime = 0;
@@ -18,16 +19,12 @@ class WaterPumpScheduler {
   ~WaterPumpScheduler();
 
   void setup();
-  void stop();
   // for simplicity and testability we are passing current time as parameter
-  void start(unsigned long runTimeMs, unsigned long currentTimeMs);
   void tick(unsigned long currentTimeMs);
 
-  // pump status
-  struct WaterPumpStatus {
-    bool isRunning;
-    unsigned long stopTime;
-  };
-  WaterPumpStatus status();
+  // Public API
+  void start(unsigned long runTimeMs, unsigned long currentTimeMs) override;
+  void stop() override;
+  WaterPumpStatus status() override;
 };
 #endif
\ No newline at end of file
diff --git a/controller/tea_poor/lib/interfaces/IEnvironment.h b/controller/tea_poor/lib/interfaces/IEnvironment.h
new file mode 100644
index 0000000..1992884
--- /dev/null
+++ b/controller/tea_poor/lib/interfaces/IEnvironment.h
@@ -0,0 +1,13 @@
+#ifndef IENVIRONMENT_H
+#define IENVIRONMENT_H
+
+#include <memory>
+
+class IEnvironment {
+  public:
+    virtual unsigned long time() const = 0;
+    virtual ~IEnvironment() {}
+};
+
+typedef std::shared_ptr<IEnvironment> IEnvironmentPtr;
+#endif // IENVIRONMENT_H
\ No newline at end of file
diff --git a/controller/tea_poor/lib/WaterPump/IWaterPump.h b/controller/tea_poor/lib/interfaces/IWaterPump.h
similarity index 100%
rename from controller/tea_poor/lib/WaterPump/IWaterPump.h
rename to controller/tea_poor/lib/interfaces/IWaterPump.h
diff --git a/controller/tea_poor/lib/interfaces/IWaterPumpSchedulerAPI.h b/controller/tea_poor/lib/interfaces/IWaterPumpSchedulerAPI.h
new file mode 100644
index 0000000..b82df7d
--- /dev/null
+++ b/controller/tea_poor/lib/interfaces/IWaterPumpSchedulerAPI.h
@@ -0,0 +1,30 @@
+// IWaterPumpSchedulerAPI interface
+#ifndef IWATERPUMPSCHEDULERAPI_H
+#define IWATERPUMPSCHEDULERAPI_H
+
+#include <memory>
+// pump status
+struct WaterPumpStatus {
+public:
+  bool isRunning;
+  unsigned long stopTime;
+  // copy constructor
+  WaterPumpStatus(const WaterPumpStatus &other) {
+    isRunning = other.isRunning;
+    stopTime = other.stopTime;
+  }
+  WaterPumpStatus(bool isRunning, unsigned long stopTime) : isRunning(isRunning), stopTime(stopTime) {}
+  // default constructor
+  WaterPumpStatus() : isRunning(false), stopTime(0) {}
+};
+
+class IWaterPumpSchedulerAPI {
+public:
+  virtual ~IWaterPumpSchedulerAPI() {}
+  virtual void stop() = 0;
+  virtual void start(unsigned long runTimeMs, unsigned long currentTimeMs) = 0;
+  virtual WaterPumpStatus status() = 0;
+};
+
+using IWaterPumpSchedulerAPIPtr = std::shared_ptr<IWaterPumpSchedulerAPI>;
+#endif
\ No newline at end of file
diff --git a/controller/tea_poor/platformio.ini b/controller/tea_poor/platformio.ini
index 82d27b9..fd827a9 100644
--- a/controller/tea_poor/platformio.ini
+++ b/controller/tea_poor/platformio.ini
@@ -28,9 +28,7 @@ build_flags =
 	-Wall	-Wextra	-Wunused
 	-static -static-libgcc -static-libstdc++
 ; ignore libraries that are only for the Arduino
-lib_ignore =
-  RemoteControl
-	WaterPumpController
+lib_ignore = Arduino
 test_ignore =  test_uno_r4_wifi
 
 [platformio]
diff --git a/controller/tea_poor/src/main.cpp b/controller/tea_poor/src/main.cpp
index b439390..3023273 100644
--- a/controller/tea_poor/src/main.cpp
+++ b/controller/tea_poor/src/main.cpp
@@ -3,94 +3,56 @@
 #include <WaterPumpController.h>
 #include <WaterPumpScheduler.h>
 #include <RemoteControl.h>
+#include <CommandProcessor.h>
 #include "secrets.h"
 
 #include <sstream>
+#include <ArduinoEnvironment.h>
+
+IEnvironmentPtr env = std::make_shared<ArduinoEnvironment>();
+
 // Setting up water pump
-WaterPumpScheduler waterPump(
+auto waterPump = std::make_shared<WaterPumpScheduler>(
   std::make_shared<WaterPumpController>(
     WATER_PUMP_DIRECTION_PIN, WATER_PUMP_BRAKE_PIN, WATER_PUMP_POWER_PIN
   )
 );
-// Just for safety reasons, we don't want to pour tea for too long
-// Their is no reason to make it configurable and add unnecessary complexity
-const int WATER_PUMP_SAFE_THRESHOLD = 10 * 1000;
 
 // setting up remote control
 RemoteControl remoteControl(WIFI_SSID, WIFI_PASSWORD);
 
-void _sendSystemStatus(std::ostream& response) {
-  response << "{";
-  // send water threshold
-  response << "\"water threshold\": " << WATER_PUMP_SAFE_THRESHOLD << ",";
-  // send water pump status
-  const auto waterPumpStatus = waterPump.status();
-  const unsigned long timeLeft = 
-    waterPumpStatus.isRunning ?
-    waterPumpStatus.stopTime - millis() :
-    0;
-  response 
-    << "\"pump\": {"
-    << "  \"running\": " << (waterPumpStatus.isRunning ? "true, " : "false, ")
-    << "  \"time left\": " << timeLeft
-    << "}";
-  // end of water pump status
-  ///////////////////////////////////
-  // send remote control status
-  response << "\"remote control\": " << remoteControl.asJSONString();
-  // end of JSON
-  response << "}";
-}
-
-bool isValidIntNumber(const char *str, const int maxValue, const int minValue=0) {
-  if (strlen(str) < 1) return false;
-  const int value = atoi(str);
-  if (value < minValue) return false;
-  if (maxValue <= value) return false;
-  return true;
-}
-
-void pour_tea(const char *milliseconds, std::ostream &res) {
-  if (!isValidIntNumber(milliseconds, WATER_PUMP_SAFE_THRESHOLD)) {
-    // send error message as JSON
-    res << "{ \"error\": \"invalid milliseconds value\" }";
-    return;
-  }
-  // start pouring tea
-  waterPump.start( atoi(milliseconds), millis() );
-  _sendSystemStatus(res);
-}
+// build command processor
+CommandProcessor commandProcessor(
+  WATER_PUMP_SAFE_THRESHOLD,
+  env,
+  waterPump
+);
 
 void setup() {
   Serial.begin(9600);
-  waterPump.setup();
-  // TODO: find a way to remove redundant code with string streams
+  waterPump->setup();
   remoteControl.setup([](Application &app) {
     app.get("/pour_tea", [](Request &req, Response &res) {
       char milliseconds[64];
       req.query("milliseconds", milliseconds, 64);
       
-      std::stringstream response;
-      pour_tea(milliseconds, response);
-      res.println(response.str().c_str());
+      const auto response = commandProcessor.pour_tea(milliseconds);
+      res.print(response.c_str());
     });
     // stop water pump
     app.get("/stop", [](Request &req, Response &res) {
-      waterPump.stop();
-      std::stringstream response;
-      _sendSystemStatus(response);
-      res.println(response.str().c_str());
+      const auto response = commandProcessor.stop();
+      res.print(response.c_str());
     });
     // get system status
     app.get("/status", [](Request &req, Response &res) {
-      std::stringstream response;
-      _sendSystemStatus(response);
-      res.println(response.str().c_str());
+      const auto response = commandProcessor.status();
+      res.print(response.c_str());
     });
   });
 }
 
 void loop() {
-  waterPump.tick(millis());
+  waterPump->tick(millis());
   remoteControl.process();
 };
\ No newline at end of file
diff --git a/controller/tea_poor/src/secrets.h.example b/controller/tea_poor/src/secrets.h.example
index 84d12c9..4fe08d5 100644
--- a/controller/tea_poor/src/secrets.h.example
+++ b/controller/tea_poor/src/secrets.h.example
@@ -11,4 +11,8 @@ const int WATER_PUMP_DIRECTION_PIN = 12;
 const int WATER_PUMP_BRAKE_PIN = 9;
 const int WATER_PUMP_POWER_PIN = 3;
 
+// Just for safety reasons, we don't want to pour tea for too long
+// Their is no reason to make it configurable and add unnecessary complexity
+const int WATER_PUMP_SAFE_THRESHOLD = 10 * 1000;
+
 #endif // SECRETS_H
\ No newline at end of file
diff --git a/controller/tea_poor/test/test_native/main.cpp b/controller/tea_poor/test/test_native/main.cpp
new file mode 100644
index 0000000..b6f09e9
--- /dev/null
+++ b/controller/tea_poor/test/test_native/main.cpp
@@ -0,0 +1,13 @@
+#include <gtest/gtest.h>
+
+// include tests
+#include "tests/WaterPumpScheduler_test.h"
+#include "tests/CommandProcessor_test.h"
+
+int main(int argc, char **argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  int result = RUN_ALL_TESTS(); // Intentionally ignoring the return value
+  (void)result; // Silence unused variable warning
+  // Always return zero-code and allow PlatformIO to parse results
+  return 0;
+}
\ No newline at end of file
diff --git a/controller/tea_poor/test/test_native/tests/CommandProcessor_test.h b/controller/tea_poor/test/test_native/tests/CommandProcessor_test.h
new file mode 100644
index 0000000..291f814
--- /dev/null
+++ b/controller/tea_poor/test/test_native/tests/CommandProcessor_test.h
@@ -0,0 +1,79 @@
+#include <gtest/gtest.h>
+#include <CommandProcessor.h>
+#include "mocks/FakeWaterPumpSchedulerAPI.h"
+#include "mocks/FakeEnvironment.h"
+
+// test that pour_tea() method returns error message if milliseconds:
+// - greater than threshold
+// - less than 0
+// - empty string
+// - not a number
+TEST(CommandProcessor, pour_tea_invalid_milliseconds) {
+  const auto EXPECTED_ERROR_MESSAGE = "{ \"error\": \"invalid milliseconds value\" }";
+  CommandProcessor commandProcessor(123, nullptr, nullptr);
+
+  // array of invalid parameters
+  const char *PARAMS[] = { "1234", "-1", "", "abc" };
+  for (auto param : PARAMS) {
+    const auto response = commandProcessor.pour_tea(param);
+    ASSERT_EQ(response, EXPECTED_ERROR_MESSAGE);
+  }
+}
+
+// test that start pouring tea by calling pour_tea() method and its stops after T milliseconds
+TEST(CommandProcessor, pour_tea) {
+  auto env = std::make_shared<FakeEnvironment>();
+  env->time(2343);
+  auto waterPump = std::make_shared<FakeWaterPumpSchedulerAPI>();
+  CommandProcessor commandProcessor(10000, env, waterPump);
+  const auto response = commandProcessor.pour_tea("1234");
+  ASSERT_EQ(waterPump->_log, "start(1234, 2343)\n");
+}
+
+// test that stop() method stops pouring tea
+TEST(CommandProcessor, stop) {
+  auto env = std::make_shared<FakeEnvironment>();
+  auto waterPump = std::make_shared<FakeWaterPumpSchedulerAPI>();
+  CommandProcessor commandProcessor(123, env, waterPump);
+  const auto response = commandProcessor.stop();
+  ASSERT_EQ(waterPump->_log, "stop()\n");
+}
+
+// test that status() method returns JSON string with water pump status
+TEST(CommandProcessor, status) {
+  auto env = std::make_shared<FakeEnvironment>();
+  auto waterPump = std::make_shared<FakeWaterPumpSchedulerAPI>();
+  CommandProcessor commandProcessor(123, env, waterPump);
+  const auto response = commandProcessor.status();
+  ASSERT_EQ(response, "{"
+    "\"water threshold\": 123, "
+    "\"pump\": {"
+    "  \"running\": false, "
+    "  \"time left\": 0"
+    "}"
+    "}"
+  );
+}
+
+// test that status() method returns JSON string with actual time left
+TEST(CommandProcessor, status_running) {
+  auto env = std::make_shared<FakeEnvironment>();
+  auto waterPump = std::make_shared<FakeWaterPumpSchedulerAPI>();
+  CommandProcessor commandProcessor(12345, env, waterPump);
+  
+  commandProcessor.pour_tea("1123");
+
+  env->time(123);
+  waterPump->_status.isRunning = true;
+  waterPump->_status.stopTime = 1123;
+  
+  const auto response = commandProcessor.status();
+  ASSERT_EQ(response, "{"
+    "\"water threshold\": 12345, "
+    "\"pump\": {"
+    "  \"running\": true, "
+    "  \"time left\": 1000"
+    "}"
+    "}"
+  );
+}
\ No newline at end of file
diff --git a/controller/tea_poor/test/test_native/WaterPumpScheduler_test.cpp b/controller/tea_poor/test/test_native/tests/WaterPumpScheduler_test.h
similarity index 67%
rename from controller/tea_poor/test/test_native/WaterPumpScheduler_test.cpp
rename to controller/tea_poor/test/test_native/tests/WaterPumpScheduler_test.h
index 2b3b40f..138541b 100644
--- a/controller/tea_poor/test/test_native/WaterPumpScheduler_test.cpp
+++ b/controller/tea_poor/test/test_native/tests/WaterPumpScheduler_test.h
@@ -1,22 +1,7 @@
-// I wasn't able to run tests at all. Run them locally and confirm that they are working.
-// Its either a local problem or a problem with the configuration of the project.
-// Further goes a sketch of the tests, but I wasn't able to run them.
 #include <gtest/gtest.h>
+#include "mocks/FakeWaterPump.h"
 #include <WaterPumpScheduler.h>
 
-// Fake water pump
-class FakeWaterPump : public IWaterPump {
-private:
-  bool _isRunning = false;
-public:
-  void setup() override { _isRunning = false; }
-  void start() override { _isRunning = true;  }
-  void stop() override  { _isRunning = false; }
-
-  bool isRunning() const override { return _isRunning; }
-};
-// End of fake water pump
-
 // test that pump is stopping after given time
 TEST(WaterPumpScheduler, test_pump_stops_after_given_time) {
   // random time between 1 and 10 seconds
@@ -61,12 +46,4 @@ TEST(WaterPumpScheduler, test_pump_is_periodically_forced_to_stop_after_given_ti
     waterPumpScheduler.tick(currentTimeMs);
     ASSERT_FALSE(fakeWaterPump->isRunning()); // pump should be stopped
   }
-}
-
-int main(int argc, char **argv) {
-  ::testing::InitGoogleTest(&argc, argv);
-  int result = RUN_ALL_TESTS(); // Intentionally ignoring the return value
-  (void)result; // Silence unused variable warning
-  // Always return zero-code and allow PlatformIO to parse results
-  return 0;
 }
\ No newline at end of file
diff --git a/controller/tea_poor/test/test_native/tests/mocks/FakeEnvironment.h b/controller/tea_poor/test/test_native/tests/mocks/FakeEnvironment.h
new file mode 100644
index 0000000..51407f8
--- /dev/null
+++ b/controller/tea_poor/test/test_native/tests/mocks/FakeEnvironment.h
@@ -0,0 +1,19 @@
+#ifndef FAKE_ENVIRONMENT_H
+#define FAKE_ENVIRONMENT_H
+
+#include <IEnvironment.h>
+
+class FakeEnvironment : public IEnvironment {
+public:
+  unsigned long time() const override {
+    return _time;
+  }
+  
+  void time(unsigned long time) {
+    _time = time;
+  }
+private:
+  unsigned long _time = 0;
+};
+
+#endif
\ No newline at end of file
diff --git a/controller/tea_poor/test/test_native/tests/mocks/FakeWaterPump.h b/controller/tea_poor/test/test_native/tests/mocks/FakeWaterPump.h
new file mode 100644
index 0000000..2dcf154
--- /dev/null
+++ b/controller/tea_poor/test/test_native/tests/mocks/FakeWaterPump.h
@@ -0,0 +1,18 @@
+#ifndef FAKE_WATER_PUMP_H
+#define FAKE_WATER_PUMP_H
+
+#include <IWaterPump.h>
+
+// Fake water pump
+class FakeWaterPump : public IWaterPump {
+private:
+  bool _isRunning = false;
+public:
+  void setup() override { _isRunning = false; }
+  void start() override { _isRunning = true;  }
+  void stop() override  { _isRunning = false; }
+
+  bool isRunning() const override { return _isRunning; }
+};
+
+#endif // FAKE_WATER_PUMP_H
\ No newline at end of file
diff --git a/controller/tea_poor/test/test_native/tests/mocks/FakeWaterPumpSchedulerAPI.h b/controller/tea_poor/test/test_native/tests/mocks/FakeWaterPumpSchedulerAPI.h
new file mode 100644
index 0000000..896046a
--- /dev/null
+++ b/controller/tea_poor/test/test_native/tests/mocks/FakeWaterPumpSchedulerAPI.h
@@ -0,0 +1,26 @@
+// FakeWaterPumpSchedulerAPI.h is a mock class for WaterPumpSchedulerAPI.h
+#ifndef FAKE_WATER_PUMP_SCHEDULER_API_H
+#define FAKE_WATER_PUMP_SCHEDULER_API_H
+
+#include <IWaterPumpSchedulerAPI.h>
+#include <string>
+
+class FakeWaterPumpSchedulerAPI : public IWaterPumpSchedulerAPI {
+public:
+  void stop() override {
+    _log += "stop()\n";
+  }
+
+  void start(unsigned long runTimeMs, unsigned long currentTimeMs) override {
+    _log += "start(" + std::to_string(runTimeMs) + ", " + std::to_string(currentTimeMs) + ")\n";
+  }
+
+  WaterPumpStatus status() override {
+    return _status;
+  }
+  
+  WaterPumpStatus _status;
+  std::string _log;
+};
+
+#endif
\ No newline at end of file

From eade444d59ae3b9dcb1b0f86d5f26e150d68f059 Mon Sep 17 00:00:00 2001
From: GreenWizard <sizeof.2011@gmail.com>
Date: Fri, 5 Jan 2024 08:14:27 +0100
Subject: [PATCH 21/21] prevent CORS issue...?

---
 controller/tea_poor/src/main.cpp | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/controller/tea_poor/src/main.cpp b/controller/tea_poor/src/main.cpp
index 3023273..7509e0b 100644
--- a/controller/tea_poor/src/main.cpp
+++ b/controller/tea_poor/src/main.cpp
@@ -28,6 +28,13 @@ CommandProcessor commandProcessor(
   waterPump
 );
 
+void withExtraHeaders(Response &res) {
+  res.set("Access-Control-Allow-Origin", "*");
+  res.set("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE");
+  res.set("Access-Control-Allow-Headers", "Content-Type");
+  res.set("Content-Type", "application/json");
+}
+
 void setup() {
   Serial.begin(9600);
   waterPump->setup();
@@ -37,16 +44,19 @@ void setup() {
       req.query("milliseconds", milliseconds, 64);
       
       const auto response = commandProcessor.pour_tea(milliseconds);
+      withExtraHeaders(res);
       res.print(response.c_str());
     });
     // stop water pump
     app.get("/stop", [](Request &req, Response &res) {
       const auto response = commandProcessor.stop();
+      withExtraHeaders(res);
       res.print(response.c_str());
     });
     // get system status
     app.get("/status", [](Request &req, Response &res) {
       const auto response = commandProcessor.status();
+      withExtraHeaders(res);
       res.print(response.c_str());
     });
   });