Skip to content

Commit

Permalink
Merge pull request #10 from GreenWizard2015/feature/add-power-control
Browse files Browse the repository at this point in the history
Pump Power Level Control Feature
  • Loading branch information
GreenWizard2015 authored Feb 1, 2024
2 parents 3f1d094 + 632f423 commit 1cec79c
Show file tree
Hide file tree
Showing 28 changed files with 482 additions and 81 deletions.
4 changes: 2 additions & 2 deletions controller/tea_poor/lib/Arduino/WaterPumpController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ void WaterPumpController::setup() {
stop();
}

void WaterPumpController::start() {
void WaterPumpController::start(int power) {
_isRunning = true;
digitalWrite(_brakePin, LOW); // release breaks
analogWrite(_powerPin, 255);
analogWrite(_powerPin, power);
}

void WaterPumpController::stop() {
Expand Down
3 changes: 1 addition & 2 deletions controller/tea_poor/lib/Arduino/WaterPumpController.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,13 @@ class WaterPumpController: public IWaterPump {
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 start(int power) override;
virtual void stop() override;

virtual bool isRunning() const override { return _isRunning; }
Expand Down
26 changes: 26 additions & 0 deletions controller/tea_poor/lib/Core/AdjustedWaterPump.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#ifndef ADJUSTEDWATERPUMP_H
#define ADJUSTEDWATERPUMP_H
#include <IWaterPump.h>
#include <math.h>

// lightweight wrapper around IWaterPump
// its purpose is to adjust power value to the range of 0..255, for now
class AdjustedWaterPump: public IWaterPump {
private:
const IWaterPumpPtr _pump;
public:
AdjustedWaterPump(IWaterPumpPtr pump) : _pump(pump) {}
virtual ~AdjustedWaterPump() override {}

virtual void setup() override { _pump->setup(); }
virtual void stop() override { _pump->stop(); }
virtual bool isRunning() const override { return _pump->isRunning(); }

virtual void start(int powerInPercents) override {
// convert percents to 0..255 range, using float
const float power = (255.0f / 100.0f) * (float)powerInPercents;
_pump->start(floor(power));
}
};

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,17 @@ std::string CommandProcessor::status() {
return response.str();
}

std::string CommandProcessor::pour_tea(const char *milliseconds) {
std::string CommandProcessor::pour_tea(const char *milliseconds, const char *power) {
if (!isValidIntNumber(milliseconds, _waterPumpSafeThreshold + 1)) {
// send error message as JSON
return std::string("{ \"error\": \"invalid milliseconds value\" }");
}
if (!isValidIntNumber(power, 101)) {
// send error message as JSON
return std::string("{ \"error\": \"invalid power value\" }");
}
// start pouring tea
_waterPump->start( atoi(milliseconds), _env->time() );
_waterPump->start( atoi(milliseconds), atoi(power) );
return status();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class CommandProcessor {
{}

std::string status();
std::string pour_tea(const char *milliseconds);
std::string pour_tea(const char *milliseconds, const char *power);
std::string stop();
private:
const int _waterPumpSafeThreshold;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#include "WaterPumpScheduler.h"

WaterPumpScheduler::WaterPumpScheduler(IWaterPumpPtr waterPump, unsigned long forceStopIntervalMs) :
WaterPumpScheduler::WaterPumpScheduler(IWaterPumpPtr waterPump, IEnvironmentPtr env, unsigned long forceStopIntervalMs) :
_waterPump(waterPump),
_env(env),
_forceStopIntervalMs(forceStopIntervalMs)
{
}
Expand All @@ -12,17 +13,18 @@ void WaterPumpScheduler::setup() {
_waterPump->setup();
}

void WaterPumpScheduler::start(unsigned long runTimeMs, unsigned long currentTimeMs) {
_stopTime = currentTimeMs + runTimeMs;
_waterPump->start();
void WaterPumpScheduler::start(unsigned long runTimeMs, int power) {
_stopTime = _env->time() + runTimeMs;
_waterPump->start(power);
}

void WaterPumpScheduler::stop() {
_waterPump->stop();
_stopTime = 0; // a bit of paranoia :)
}

void WaterPumpScheduler::tick(unsigned long currentTimeMs) {
void WaterPumpScheduler::tick() {
const auto currentTimeMs = _env->time();
if (_stopTime <= currentTimeMs) {
stop();
_stopTime = currentTimeMs + _forceStopIntervalMs; // force stop after X milliseconds
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,30 @@

#include <IWaterPump.h>
#include <IWaterPumpSchedulerAPI.h>
#include <IEnvironment.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;
IEnvironmentPtr _env;
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(IWaterPumpPtr waterPump, IEnvironmentPtr env, unsigned long forceStopIntervalMs);
// forceStopIntervalMs is set to 1000ms by default
WaterPumpScheduler(IWaterPumpPtr waterPump, IEnvironmentPtr env) : WaterPumpScheduler(waterPump, env, 1000) {}
~WaterPumpScheduler();

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

// Public API
void start(unsigned long runTimeMs, unsigned long currentTimeMs) override;
void start(unsigned long runTimeMs, int power) override;
void stop() override;
WaterPumpStatus status() override;
};
Expand Down
2 changes: 1 addition & 1 deletion controller/tea_poor/lib/interfaces/IWaterPump.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class IWaterPump {
virtual ~IWaterPump() {}

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

virtual bool isRunning() const = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class IWaterPumpSchedulerAPI {
public:
virtual ~IWaterPumpSchedulerAPI() {}
virtual void stop() = 0;
virtual void start(unsigned long runTimeMs, unsigned long currentTimeMs) = 0;
virtual void start(unsigned long runTimeMs, int power) = 0;
virtual WaterPumpStatus status() = 0;
};

Expand Down
20 changes: 13 additions & 7 deletions controller/tea_poor/src/main.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <Arduino.h>
#include <memory>
#include <WaterPumpController.h>
#include <AdjustedWaterPump.h>
#include <WaterPumpScheduler.h>
#include <RemoteControl.h>
#include <CommandProcessor.h>
Expand All @@ -9,13 +10,15 @@
#include <sstream>
#include <ArduinoEnvironment.h>

IEnvironmentPtr env = std::make_shared<ArduinoEnvironment>();
const auto env = std::make_shared<ArduinoEnvironment>();

// Setting up water pump
auto waterPump = std::make_shared<WaterPumpScheduler>(
std::make_shared<WaterPumpController>(
WATER_PUMP_DIRECTION_PIN, WATER_PUMP_BRAKE_PIN, WATER_PUMP_POWER_PIN
)
const auto waterPump = std::make_shared<WaterPumpScheduler>(
std::make_shared<AdjustedWaterPump>(
std::make_shared<WaterPumpController>(
WATER_PUMP_DIRECTION_PIN, WATER_PUMP_BRAKE_PIN, WATER_PUMP_POWER_PIN
)
), env
);

// build command processor
Expand Down Expand Up @@ -46,8 +49,11 @@ RemoteControl remoteControl(
app.get("/pour_tea", [](Request &req, Response &res) {
char milliseconds[64];
req.query("milliseconds", milliseconds, 64);

char power[64];
req.query("powerLevel", power, 64);

const auto response = commandProcessor.pour_tea(milliseconds);
const auto response = commandProcessor.pour_tea(milliseconds, power);
withExtraHeaders(res);
res.print(response.c_str());
});
Expand All @@ -73,6 +79,6 @@ void setup() {
}

void loop() {
waterPump->tick(millis());
waterPump->tick();
remoteControl.process();
};
1 change: 1 addition & 0 deletions controller/tea_poor/test/test_native/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// include tests
#include "tests/WaterPumpScheduler_test.h"
#include "tests/CommandProcessor_test.h"
#include "tests/AdjustedWaterPump_test.h"

int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#include <gtest/gtest.h>
#include "mocks/FakeWaterPump.h"
#include <AdjustedWaterPump.h>

// test that pumps power passed as percents is converted to 0..255 range
TEST(AdjustedWaterPump, test_pumps_power_passed_as_percents_is_converted_to_0_255_range) {
const auto fakeWaterPump = std::make_shared<FakeWaterPump>();
AdjustedWaterPump adjustedWaterPump(fakeWaterPump);
// list of pairs: (powerInPercents, expectedPower)
const std::vector<std::pair<int, int>> tests = {
{0, 0}, {1, 2}, {2, 5},
{50, 127}, {100, 255}
};
for(const auto& test: tests) {
adjustedWaterPump.start(test.first);
ASSERT_EQ(fakeWaterPump->power(), test.second);
}
}
57 changes: 36 additions & 21 deletions controller/tea_poor/test/test_native/tests/CommandProcessor_test.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,68 @@
#include "mocks/FakeWaterPumpSchedulerAPI.h"
#include "mocks/FakeEnvironment.h"

const auto VALID_POWER = "100";
const auto INVALID_TIME_ERROR_MESSAGE = "{ \"error\": \"invalid milliseconds value\" }";
const auto INVALID_POWER_ERROR_MESSAGE = "{ \"error\": \"invalid power value\" }";
// 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) {
CommandProcessor commandProcessor(123, nullptr, nullptr);
ASSERT_EQ(commandProcessor.pour_tea("1234"), INVALID_TIME_ERROR_MESSAGE);
ASSERT_EQ(commandProcessor.pour_tea("-1"), INVALID_TIME_ERROR_MESSAGE);
ASSERT_EQ(commandProcessor.pour_tea(""), INVALID_TIME_ERROR_MESSAGE);
ASSERT_EQ(commandProcessor.pour_tea("abc"), INVALID_TIME_ERROR_MESSAGE);
ASSERT_EQ(commandProcessor.pour_tea("1234", VALID_POWER), INVALID_TIME_ERROR_MESSAGE);
ASSERT_EQ(commandProcessor.pour_tea("-1", VALID_POWER), INVALID_TIME_ERROR_MESSAGE);
ASSERT_EQ(commandProcessor.pour_tea("", VALID_POWER), INVALID_TIME_ERROR_MESSAGE);
ASSERT_EQ(commandProcessor.pour_tea("abc", VALID_POWER), INVALID_TIME_ERROR_MESSAGE);
}

// test that pour_tea() method returns error message if power:
// - greater than 100
// - less than 0
// - empty string
// - not a number
TEST(CommandProcessor, pour_tea_invalid_power) {
CommandProcessor commandProcessor(123, nullptr, nullptr);
ASSERT_EQ(commandProcessor.pour_tea("123", "101"), INVALID_POWER_ERROR_MESSAGE);
ASSERT_EQ(commandProcessor.pour_tea("123", "-1"), INVALID_POWER_ERROR_MESSAGE);
ASSERT_EQ(commandProcessor.pour_tea("123", ""), INVALID_POWER_ERROR_MESSAGE);
ASSERT_EQ(commandProcessor.pour_tea("123", "abc"), INVALID_POWER_ERROR_MESSAGE);
}

// for simplicity of the UI, we should accept as valid 0 and exactly threshold value
TEST(CommandProcessor, pour_tea_valid_boundary_values) {
auto env = std::make_shared<FakeEnvironment>();
auto waterPump = std::make_shared<FakeWaterPumpSchedulerAPI>();
const auto env = std::make_shared<FakeEnvironment>();
const auto waterPump = std::make_shared<FakeWaterPumpSchedulerAPI>(env);
CommandProcessor commandProcessor(123, env, waterPump);

ASSERT_NE(commandProcessor.pour_tea("0"), INVALID_TIME_ERROR_MESSAGE);
ASSERT_NE(commandProcessor.pour_tea("123"), INVALID_TIME_ERROR_MESSAGE);
ASSERT_NE(commandProcessor.pour_tea("0", VALID_POWER), INVALID_TIME_ERROR_MESSAGE);
ASSERT_NE(commandProcessor.pour_tea("123", VALID_POWER), INVALID_TIME_ERROR_MESSAGE);
}

// test that start pouring tea by calling pour_tea() method and its stops after T milliseconds
// test that start pouring tea by calling pour_tea() method with specified parameters
TEST(CommandProcessor, pour_tea) {
auto env = std::make_shared<FakeEnvironment>();
const auto env = std::make_shared<FakeEnvironment>();
const auto waterPump = std::make_shared<FakeWaterPumpSchedulerAPI>(env);
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");
const auto response = commandProcessor.pour_tea("1234", "23");
ASSERT_EQ(waterPump->_log, "start(1234, 23, 2343)\n");
}

// test that stop() method stops pouring tea
TEST(CommandProcessor, stop) {
auto env = std::make_shared<FakeEnvironment>();
auto waterPump = std::make_shared<FakeWaterPumpSchedulerAPI>();
const auto env = std::make_shared<FakeEnvironment>();
const auto waterPump = std::make_shared<FakeWaterPumpSchedulerAPI>(env);
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>();
const auto env = std::make_shared<FakeEnvironment>();
const auto waterPump = std::make_shared<FakeWaterPumpSchedulerAPI>(env);
CommandProcessor commandProcessor(123, env, waterPump);
const auto response = commandProcessor.status();
ASSERT_EQ(response, "{"
Expand All @@ -65,11 +80,11 @@ TEST(CommandProcessor, status) {

// 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>();
const auto env = std::make_shared<FakeEnvironment>();
const auto waterPump = std::make_shared<FakeWaterPumpSchedulerAPI>(env);
CommandProcessor commandProcessor(12345, env, waterPump);

commandProcessor.pour_tea("1123");
commandProcessor.pour_tea("1123", "100");

env->time(123);
waterPump->_status.isRunning = true;
Expand Down
Loading

0 comments on commit 1cec79c

Please sign in to comment.