Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Async control & api update #3

Merged
merged 23 commits into from
Jan 7, 2024
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
aff9e62
Basic impl. with a bugs
GreenWizard2015 Dec 28, 2023
f54a375
would this fix CI?
GreenWizard2015 Dec 28, 2023
c8c2e45
maybe now?
GreenWizard2015 Dec 28, 2023
c6003dc
last try
GreenWizard2015 Dec 28, 2023
815c496
pls?
GreenWizard2015 Dec 28, 2023
54b8e58
remove local and attempt to switch to unity framework
omonrise Dec 29, 2023
0f8ea31
Dependency Unity for testing should not be part of build dependencies…
psmgeelen Dec 29, 2023
5e0bbcf
remove unity from libdeps
omonrise Dec 29, 2023
4d1bab3
Merge branch 'feature/async_control' of https://github.com/psmgeelen/…
omonrise Dec 29, 2023
3288759
fix everything?
GreenWizard2015 Dec 29, 2023
7f01633
default env for platformio build
GreenWizard2015 Dec 29, 2023
6dd0463
configure CI to build only for Android
GreenWizard2015 Dec 29, 2023
7c12ef0
fix
GreenWizard2015 Dec 29, 2023
e2f62b6
fix typo
GreenWizard2015 Dec 29, 2023
7761f46
Separate out CI
psmgeelen Dec 29, 2023
7046cf4
Update user authentication logic
GreenWizard2015 Dec 30, 2023
89347f0
shared_ptr + return error as JSON
GreenWizard2015 Dec 30, 2023
8eb199d
Merge branch 'feature/async_control' of github.com:psmgeelen/projectt…
GreenWizard2015 Dec 30, 2023
63bee21
small rework
GreenWizard2015 Dec 30, 2023
311e314
extracted secrets from main.cpp
GreenWizard2015 Dec 30, 2023
75a7629
fix CI
GreenWizard2015 Dec 30, 2023
267322c
very big update. Reorganized the code, added a command processor, tests.
GreenWizard2015 Dec 30, 2023
eade444
prevent CORS issue...?
GreenWizard2015 Jan 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/platformio_build.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: PlatformIO CI - Build for Arduino

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

# 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
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: PlatformIO CI
name: PlatformIO CI - Unit Tests

on: [push]

@@ -20,6 +20,6 @@ jobs:
- name: Install PlatformIO Core
run: pip install --upgrade platformio

- name: Build PlatformIO Project
- name: Run tests on the native platform
working-directory: ./controller/tea_poor
run: pio run
run: pio test -e native
3 changes: 3 additions & 0 deletions controller/tea_poor/.gitignore
Original file line number Diff line number Diff line change
@@ -3,3 +3,6 @@
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch

# hide secrets
src/secrets.h
15 changes: 15 additions & 0 deletions controller/tea_poor/lib/Arduino/ArduinoEnvironment.h
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@ class RemoteControl {
~RemoteControl();
void setup(RemoteControlRoutesCallback routes);
void process();
String asJSONString() const;
private:
const String _SSID;
const String _SSIDPassword;
Original file line number Diff line number Diff line change
@@ -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;
}
23 changes: 23 additions & 0 deletions controller/tea_poor/lib/Arduino/WaterPumpController.h
Original file line number Diff line number Diff line change
@@ -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
58 changes: 58 additions & 0 deletions controller/tea_poor/lib/CommandProcessor/CommandProcessor.cpp
Original file line number Diff line number Diff line change
@@ -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();
}
30 changes: 30 additions & 0 deletions controller/tea_poor/lib/CommandProcessor/CommandProcessor.h
Original file line number Diff line number Diff line change
@@ -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
28 changes: 0 additions & 28 deletions controller/tea_poor/lib/WaterPumpController/WaterPumpController.h

This file was deleted.

34 changes: 34 additions & 0 deletions controller/tea_poor/lib/WaterPumpScheduler/WaterPumpScheduler.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#include "WaterPumpScheduler.h"

WaterPumpScheduler::WaterPumpScheduler(IWaterPumpPtr waterPump, unsigned long forceStopIntervalMs) :
_waterPump(waterPump),
_forceStopIntervalMs(forceStopIntervalMs)
{
}

WaterPumpScheduler::~WaterPumpScheduler() {}

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
}
}

WaterPumpStatus WaterPumpScheduler::status() {
return WaterPumpStatus(_waterPump->isRunning(), _stopTime);
}
30 changes: 30 additions & 0 deletions controller/tea_poor/lib/WaterPumpScheduler/WaterPumpScheduler.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#ifndef WATERPUMPSCHEDULER_H
#define WATERPUMPSCHEDULER_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 : public IWaterPumpSchedulerAPI {
private:
IWaterPumpPtr _waterPump;
unsigned long _stopTime = 0;
// each X milliseconds will force stop water pump
unsigned long _forceStopIntervalMs;
public:
WaterPumpScheduler(IWaterPumpPtr waterPump, unsigned long forceStopIntervalMs);
WaterPumpScheduler(IWaterPumpPtr waterPump) : WaterPumpScheduler(waterPump, 1000) {}
~WaterPumpScheduler();

void setup();
// for simplicity and testability we are passing current time as parameter
void tick(unsigned long currentTimeMs);

// Public API
void start(unsigned long runTimeMs, unsigned long currentTimeMs) override;
void stop() override;
WaterPumpStatus status() override;
};
#endif
13 changes: 13 additions & 0 deletions controller/tea_poor/lib/interfaces/IEnvironment.h
Original file line number Diff line number Diff line change
@@ -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
19 changes: 19 additions & 0 deletions controller/tea_poor/lib/interfaces/IWaterPump.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#ifndef IWATERPUMP_H
#define IWATERPUMP_H

#include <memory>

class IWaterPump {
public:
virtual ~IWaterPump() {}

virtual void setup() = 0;
virtual void start() = 0;
virtual void stop() = 0;

virtual bool isRunning() const = 0;
};

// define shared pointer alias
using IWaterPumpPtr = std::shared_ptr<IWaterPump>;
#endif
30 changes: 30 additions & 0 deletions controller/tea_poor/lib/interfaces/IWaterPumpSchedulerAPI.h
Original file line number Diff line number Diff line change
@@ -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
19 changes: 19 additions & 0 deletions controller/tea_poor/platformio.ini
Original file line number Diff line number Diff line change
@@ -14,3 +14,22 @@ board = uno_r4_wifi
framework = arduino
lib_deps =
lasselukkari/aWOT@^3.5.0
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 = Arduino
test_ignore = test_uno_r4_wifi

[platformio]
default_envs = uno_r4_wifi
82 changes: 48 additions & 34 deletions controller/tea_poor/src/main.cpp
Original file line number Diff line number Diff line change
@@ -1,54 +1,68 @@
#include <Arduino.h>
#include <memory>
#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
WaterPumpController 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;
auto waterPump = std::make_shared<WaterPumpScheduler>(
std::make_shared<WaterPumpController>(
WATER_PUMP_DIRECTION_PIN, WATER_PUMP_BRAKE_PIN, WATER_PUMP_POWER_PIN
)
);

// setting up remote control
RemoteControl remoteControl(
"MyWiFiNetwork", // network name/SSID
"VerySecurePassword" // network password
);
RemoteControl remoteControl(WIFI_SSID, WIFI_PASSWORD);

bool isValidIntNumber(const char *str, const int maxValue, const int minValue=0) {
if (strlen(str) <= 0) return false;
const int value = atoi(str);
if (value < minValue) return false;
if (maxValue <= value) return false;
return true;
}
// build command processor
CommandProcessor commandProcessor(
WATER_PUMP_SAFE_THRESHOLD,
env,
waterPump
);

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);
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!");
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);
waterPumpController.setup();
waterPump->setup();
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);

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());
});
});
}

void loop() {
waterPump->tick(millis());
remoteControl.process();
};
18 changes: 18 additions & 0 deletions controller/tea_poor/src/secrets.h.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// 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;

// 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
13 changes: 13 additions & 0 deletions controller/tea_poor/test/test_native/main.cpp
Original file line number Diff line number Diff line change
@@ -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;
}
79 changes: 79 additions & 0 deletions controller/tea_poor/test/test_native/tests/CommandProcessor_test.h
Original file line number Diff line number Diff line change
@@ -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"
"}"
"}"
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#include <gtest/gtest.h>
#include "mocks/FakeWaterPump.h"
#include <WaterPumpScheduler.h>

// 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;
IWaterPumpPtr fakeWaterPump = std::make_shared<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) {
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

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
}
}
19 changes: 19 additions & 0 deletions controller/tea_poor/test/test_native/tests/mocks/FakeEnvironment.h
Original file line number Diff line number Diff line change
@@ -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
18 changes: 18 additions & 0 deletions controller/tea_poor/test/test_native/tests/mocks/FakeWaterPump.h
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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