Skip to content

Commit 3f1d094

Browse files
Merge pull request psmgeelen#6 from psmgeelen/feature/ui/countdown
Feature/UI/countdown
2 parents 9b384bd + a5b064d commit 3f1d094

28 files changed

+590
-204
lines changed

controller/tea_poor/lib/Arduino/RemoteControl.cpp

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,7 @@ void debugNetworkInfo() {
3838
Serial.println();
3939
}
4040

41-
RemoteControl::RemoteControl(const char* SSID, const char* SSIDPassword) :
42-
_SSID(SSID), _SSIDPassword(SSIDPassword),
43-
_server(80), _app()
44-
{
45-
}
46-
47-
RemoteControl::~RemoteControl() {
48-
}
49-
50-
void RemoteControl::_setupNetwork() {
41+
void verifyNetwork() {
5142
if (WiFi.status() == WL_NO_MODULE) {
5243
Serial.println("Communication with WiFi module failed!");
5344
while(true) delay(500);
@@ -59,15 +50,25 @@ void RemoteControl::_setupNetwork() {
5950
Serial.println(WIFI_FIRMWARE_LATEST_VERSION);
6051
Serial.println("Please upgrade your firmware.");
6152
}
53+
}
54+
55+
RemoteControl::RemoteControl(const NetworkConnectCallback &onConnect) :
56+
_onConnect(onConnect)
57+
{
58+
}
6259

60+
RemoteControl::~RemoteControl() {
61+
}
62+
63+
void RemoteControl::connectTo(const char* ssid, const char* password) {
6364
Serial.print("Connecting to ");
64-
Serial.println(_SSID);
65+
Serial.println(ssid);
6566

6667
int attempts = 0;
6768
while (WL_CONNECTED != WiFi.status()) { // try to connect to the network
6869
attempts++;
69-
Serial.println("Atempt to connect: " + String(attempts));
70-
WiFi.begin(_SSID.c_str(), _SSIDPassword.c_str());
70+
Serial.println("Attempt to connect: " + String(attempts));
71+
WiFi.begin(ssid, password);
7172
for (int i = 0; i < 50; i++) { // wait for connection
7273
Serial.print(".");
7374
delay(500);
@@ -77,30 +78,33 @@ void RemoteControl::_setupNetwork() {
7778
Serial.println("Connection status: " + String(WiFi.status()));
7879
}
7980
Serial.println();
80-
81+
// successfully connected
8182
debugNetworkInfo();
8283
}
8384

84-
void RemoteControl::setup(RemoteControlRoutesCallback routes) {
85-
_setupNetwork();
86-
routes(_app); // setup routes
85+
void RemoteControl::setup() { reconnect(); }
86+
87+
void RemoteControl::reconnect() {
88+
// reset everything
89+
WiFi.disconnect();
90+
verifyNetwork();
91+
_app = Application(); // reset routes
92+
_server = WiFiServer(80); // reset server
93+
// reconnect
94+
_onConnect(*this, _app);
8795
_server.begin();
8896
}
8997

9098
void RemoteControl::process() {
91-
// TODO: check if we still have a connection. If not, reconnect.
99+
if(WL_CONNECTED != WiFi.status()) {
100+
reconnect();
101+
return; // wait for next tick, just to be sure that all is ok
102+
}
103+
///////////////////////////
92104
WiFiClient client = _server.available();
93105

94106
if (client.connected()) {
95107
_app.process(&client);
96108
client.stop();
97109
}
98-
}
99-
100-
String RemoteControl::asJSONString() const {
101-
String result = "{";
102-
result += "\"SSID\": \"" + _SSID + "\",";
103-
result += "\"signal strength\": " + String(WiFi.RSSI());
104-
result += "}";
105-
return result;
106110
}

controller/tea_poor/lib/Arduino/RemoteControl.h

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,25 @@
44
#include <Arduino.h>
55
#include <WiFiS3.h>
66
#include <aWOT.h>
7+
#include <functional>
78

8-
// define routes callback function signature
9-
typedef void (*RemoteControlRoutesCallback)(Application &app);
9+
// forward declaration
10+
class RemoteControl;
11+
12+
// define callback for (re)connecting to WiFi, use std::function
13+
typedef std::function<void(RemoteControl&, Application&)> NetworkConnectCallback;
1014

1115
class RemoteControl {
1216
public:
13-
RemoteControl(const char* SSID, const char* SSIDPassword);
17+
RemoteControl(const NetworkConnectCallback &onConnect);
1418
~RemoteControl();
15-
void setup(RemoteControlRoutesCallback routes);
19+
void setup();
1620
void process();
17-
String asJSONString() const;
21+
void reconnect();
22+
///////////////////
23+
void connectTo(const char* ssid, const char* password);
1824
private:
19-
const String _SSID;
20-
const String _SSIDPassword;
25+
NetworkConnectCallback _onConnect;
2126
WiFiServer _server;
2227
Application _app;
2328

controller/tea_poor/lib/Arduino/WaterPumpController.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ WaterPumpController::WaterPumpController(int directionPin, int brakePin, int pow
1212
WaterPumpController::~WaterPumpController() {}
1313

1414
void WaterPumpController::setup() {
15-
pinMode(_directionPin, OUTPUT);
15+
// NOTE: we use one-directional motor, so we can't use direction pin
16+
// but I keep it here for future reference
17+
// pinMode(_directionPin, OUTPUT);
1618
pinMode(_brakePin, OUTPUT);
1719
pinMode(_powerPin, OUTPUT);
1820
stop();

controller/tea_poor/lib/CommandProcessor/CommandProcessor.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ bool isValidIntNumber(const char *str, const int maxValue, const int minValue=0)
2020

2121
std::string CommandProcessor::status() {
2222
std::stringstream response;
23+
const auto now = _env->time();
2324
response << "{";
25+
// send current time in milliseconds to synchronize time on client side
26+
response << "\"time\": " << now << ", ";
2427
// send water threshold
2528
response << "\"water threshold\": " << _waterPumpSafeThreshold << ", ";
2629
// send water pump status
2730
const auto waterPumpStatus = _waterPump->status();
28-
const auto now = _env->time();
2931
const auto timeLeft = waterPumpStatus.isRunning ? waterPumpStatus.stopTime - now : 0;
3032
response
3133
<< "\"pump\": {"
@@ -43,7 +45,7 @@ std::string CommandProcessor::status() {
4345
}
4446

4547
std::string CommandProcessor::pour_tea(const char *milliseconds) {
46-
if (!isValidIntNumber(milliseconds, _waterPumpSafeThreshold)) {
48+
if (!isValidIntNumber(milliseconds, _waterPumpSafeThreshold + 1)) {
4749
// send error message as JSON
4850
return std::string("{ \"error\": \"invalid milliseconds value\" }");
4951
}

controller/tea_poor/src/main.cpp

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@ auto waterPump = std::make_shared<WaterPumpScheduler>(
1818
)
1919
);
2020

21-
// setting up remote control
22-
RemoteControl remoteControl(WIFI_SSID, WIFI_PASSWORD);
23-
2421
// build command processor
2522
CommandProcessor commandProcessor(
2623
WATER_PUMP_SAFE_THRESHOLD,
@@ -35,10 +32,17 @@ void withExtraHeaders(Response &res) {
3532
res.set("Content-Type", "application/json");
3633
}
3734

38-
void setup() {
39-
Serial.begin(9600);
40-
waterPump->setup();
41-
remoteControl.setup([](Application &app) {
35+
RemoteControl remoteControl(
36+
// lambda function to setup network
37+
[](RemoteControl &remoteControl, Application &app) {
38+
// connect to WiFi
39+
// set static IP address, if defined in configs
40+
#ifdef WIFI_IP_ADDRESS
41+
WiFi.config(WIFI_IP_ADDRESS);
42+
#endif
43+
44+
remoteControl.connectTo(WIFI_SSID, WIFI_PASSWORD);
45+
// setup routes
4246
app.get("/pour_tea", [](Request &req, Response &res) {
4347
char milliseconds[64];
4448
req.query("milliseconds", milliseconds, 64);
@@ -59,7 +63,13 @@ void setup() {
5963
withExtraHeaders(res);
6064
res.print(response.c_str());
6165
});
62-
});
66+
}
67+
);
68+
69+
void setup() {
70+
Serial.begin(9600);
71+
waterPump->setup();
72+
remoteControl.setup();
6373
}
6474

6575
void loop() {

controller/tea_poor/src/secrets.h.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,7 @@ const int WATER_PUMP_POWER_PIN = 3;
1515
// Their is no reason to make it configurable and add unnecessary complexity
1616
const int WATER_PUMP_SAFE_THRESHOLD = 10 * 1000;
1717

18+
// Static IP address. If not defined, dynamic IP address will be used
19+
// #define WIFI_IP_ADDRESS IPAddress(192, 168, 1, 123)
20+
1821
#endif // SECRETS_H

controller/tea_poor/test/test_native/tests/CommandProcessor_test.h

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,28 @@
33
#include "mocks/FakeWaterPumpSchedulerAPI.h"
44
#include "mocks/FakeEnvironment.h"
55

6+
const auto INVALID_TIME_ERROR_MESSAGE = "{ \"error\": \"invalid milliseconds value\" }";
67
// test that pour_tea() method returns error message if milliseconds:
78
// - greater than threshold
89
// - less than 0
910
// - empty string
1011
// - not a number
1112
TEST(CommandProcessor, pour_tea_invalid_milliseconds) {
12-
const auto EXPECTED_ERROR_MESSAGE = "{ \"error\": \"invalid milliseconds value\" }";
1313
CommandProcessor commandProcessor(123, nullptr, nullptr);
14+
ASSERT_EQ(commandProcessor.pour_tea("1234"), INVALID_TIME_ERROR_MESSAGE);
15+
ASSERT_EQ(commandProcessor.pour_tea("-1"), INVALID_TIME_ERROR_MESSAGE);
16+
ASSERT_EQ(commandProcessor.pour_tea(""), INVALID_TIME_ERROR_MESSAGE);
17+
ASSERT_EQ(commandProcessor.pour_tea("abc"), INVALID_TIME_ERROR_MESSAGE);
18+
}
19+
20+
// for simplicity of the UI, we should accept as valid 0 and exactly threshold value
21+
TEST(CommandProcessor, pour_tea_valid_boundary_values) {
22+
auto env = std::make_shared<FakeEnvironment>();
23+
auto waterPump = std::make_shared<FakeWaterPumpSchedulerAPI>();
24+
CommandProcessor commandProcessor(123, env, waterPump);
1425

15-
// array of invalid parameters
16-
const char *PARAMS[] = { "1234", "-1", "", "abc" };
17-
for (auto param : PARAMS) {
18-
const auto response = commandProcessor.pour_tea(param);
19-
ASSERT_EQ(response, EXPECTED_ERROR_MESSAGE);
20-
}
26+
ASSERT_NE(commandProcessor.pour_tea("0"), INVALID_TIME_ERROR_MESSAGE);
27+
ASSERT_NE(commandProcessor.pour_tea("123"), INVALID_TIME_ERROR_MESSAGE);
2128
}
2229

2330
// test that start pouring tea by calling pour_tea() method and its stops after T milliseconds
@@ -46,6 +53,7 @@ TEST(CommandProcessor, status) {
4653
CommandProcessor commandProcessor(123, env, waterPump);
4754
const auto response = commandProcessor.status();
4855
ASSERT_EQ(response, "{"
56+
"\"time\": 0, "
4957
"\"water threshold\": 123, "
5058
"\"pump\": {"
5159
" \"running\": false, "
@@ -69,6 +77,7 @@ TEST(CommandProcessor, status_running) {
6977

7078
const auto response = commandProcessor.status();
7179
ASSERT_EQ(response, "{"
80+
"\"time\": 123, "
7281
"\"water threshold\": 12345, "
7382
"\"pump\": {"
7483
" \"running\": true, "

ui/public/valve.png

23.7 KB
Loading

ui/src/App.css

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,15 @@
11
.App {
2+
}
3+
4+
.countdown-area {
5+
width: 100%;
6+
text-align: center;
7+
font-weight: bold;
8+
font-size: 2rem;
9+
}
10+
11+
.hold-to-pour-image {
12+
object-fit: contain;
13+
width: 25%;
14+
height: auto;
215
}

ui/src/App.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ import { Container, Form } from 'react-bootstrap';
44
import { connect } from 'react-redux';
55

66
import NotificationsArea from './components/NotificationsArea.js';
7-
import APIAddressField from './components/APIAddressField';
8-
import PourTimeField from './components/PourTimeField';
9-
import SystemControls from './components/SystemControls';
10-
import SystemStatusArea from './components/SystemStatusArea';
7+
import APIAddressField from './components/APIAddressField.js';
8+
import PourTimeField from './components/PourTimeField.js';
9+
import SystemControls from './components/SystemControls.js';
10+
import SystemStatusArea from './components/SystemStatusArea.js';
11+
import CurrentOperationInfoArea from './components/CurrentOperationInfoArea.js';
12+
import HoldToPour from './components/HoldToPour.js';
1113

1214
function App({ isConnected }) {
13-
// TODO: Add a fake countdown timer of timeLeft
1415
return (
1516
<Container className="App">
1617
<h1>Tea System UI</h1>
@@ -21,7 +22,9 @@ function App({ isConnected }) {
2122
{isConnected ? (
2223
<>
2324
<PourTimeField />
25+
<CurrentOperationInfoArea />
2426
<SystemControls />
27+
<HoldToPour />
2528
</>
2629
) : null}
2730
</Form>

ui/src/App.test.js

Lines changed: 0 additions & 6 deletions
This file was deleted.

ui/src/Utils/time.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
function toTimeStr(diff) {
2+
const seconds = Math.floor(diff / 1000);
3+
const minutes = Math.floor(seconds / 60);
4+
const hours = Math.floor(minutes / 60);
5+
6+
const secondsStr = (seconds % 60).toString().padStart(2, '0');
7+
const minutesStr = (minutes % 60).toString().padStart(2, '0');
8+
const hoursStr = hours.toString().padStart(2, '0');
9+
10+
return `${hoursStr}:${minutesStr}:${secondsStr}`;
11+
}
12+
13+
export function timeBetweenAsString({endTime=null, startTime=null, bounded=false}) {
14+
if (null === startTime) startTime = new Date();
15+
if (null === endTime) endTime = new Date();
16+
17+
let diff = endTime - startTime; // in ms
18+
if (bounded && (diff < 0)) diff = 0;
19+
20+
if (diff < 0) return '-' + toTimeStr(-diff);
21+
return toTimeStr(diff);
22+
}

0 commit comments

Comments
 (0)