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 #include +#include #include // 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 +#include + +// 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