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
Show file tree
Hide file tree
Changes from 12 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
7 changes: 5 additions & 2 deletions .github/workflows/platformio.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
GreenWizard2015 marked this conversation as resolved.
Show resolved Hide resolved
working-directory: ./controller/tea_poor
run: pio run
run: pio run -e uno_r4_wifi
8 changes: 8 additions & 0 deletions controller/tea_poor/lib/RemoteControl/RemoteControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
1 change: 1 addition & 0 deletions controller/tea_poor/lib/RemoteControl/RemoteControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class RemoteControl {
~RemoteControl();
void setup(RemoteControlRoutesCallback routes);
void process();
String asJSONString() const;
private:
const String _SSID;
const String _SSIDPassword;
Expand Down
15 changes: 15 additions & 0 deletions controller/tea_poor/lib/WaterPump/IWaterPump.h
Original file line number Diff line number Diff line change
@@ -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
42 changes: 42 additions & 0 deletions controller/tea_poor/lib/WaterPump/WaterPumpScheduler.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#include "WaterPumpScheduler.h"

WaterPumpScheduler::WaterPumpScheduler(IWaterPump* 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;
}

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
};
}
33 changes: 33 additions & 0 deletions controller/tea_poor/lib/WaterPump/WaterPumpScheduler.h
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
25 changes: 10 additions & 15 deletions controller/tea_poor/lib/WaterPumpController/WaterPumpController.h
Original file line number Diff line number Diff line change
@@ -1,28 +1,23 @@
#ifndef WATERPUMPCONTROLLER_H
#define WATERPUMPCONTROLLER_H
#include <IWaterPump.h>

class WaterPumpController {
public:
enum EPumpState {
PUMP_OFF,
PUMP_ON
};
class WaterPumpController: public IWaterPump {
private:
const int _directionPin;
const int _brakePin;
const int _powerPin;
const int _maxPower = 255;
EPumpState _state = PUMP_OFF;
bool _isRunning = false;
public:
WaterPumpController(int directionPin, int brakePin, int powerPin);
~WaterPumpController();

void setup();
void pour(int miliseconds);
void startPump();
void stopPump();
virtual ~WaterPumpController() override;

virtual void setup() override;
virtual void start() override;
virtual void stop() override;

EPumpState state() const { return _state; }
virtual bool isRunning() const override { return _isRunning; }
};

#endif // WATERPUMPCONTROLLER_H
#endif
21 changes: 21 additions & 0 deletions controller/tea_poor/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,24 @@ 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 =
RemoteControl
WaterPumpController
test_ignore = test_uno_r4_wifi

[platformio]
default_envs = uno_r4_wifi
60 changes: 49 additions & 11 deletions controller/tea_poor/src/main.cpp
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -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();
};
72 changes: 72 additions & 0 deletions controller/tea_poor/test/test_native/WaterPumpScheduler_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// 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() 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
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);
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;
}
Loading