Skip to content

Commit

Permalink
Merge pull request #6 from psmgeelen/feature/ui/countdown
Browse files Browse the repository at this point in the history
Feature/UI/countdown
  • Loading branch information
GreenWizard2015 authored Jan 28, 2024
2 parents 9b384bd + a5b064d commit 3f1d094
Show file tree
Hide file tree
Showing 28 changed files with 590 additions and 204 deletions.
56 changes: 30 additions & 26 deletions controller/tea_poor/lib/Arduino/RemoteControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,7 @@ void debugNetworkInfo() {
Serial.println();
}

RemoteControl::RemoteControl(const char* SSID, const char* SSIDPassword) :
_SSID(SSID), _SSIDPassword(SSIDPassword),
_server(80), _app()
{
}

RemoteControl::~RemoteControl() {
}

void RemoteControl::_setupNetwork() {
void verifyNetwork() {
if (WiFi.status() == WL_NO_MODULE) {
Serial.println("Communication with WiFi module failed!");
while(true) delay(500);
Expand All @@ -59,15 +50,25 @@ void RemoteControl::_setupNetwork() {
Serial.println(WIFI_FIRMWARE_LATEST_VERSION);
Serial.println("Please upgrade your firmware.");
}
}

RemoteControl::RemoteControl(const NetworkConnectCallback &onConnect) :
_onConnect(onConnect)
{
}

RemoteControl::~RemoteControl() {
}

void RemoteControl::connectTo(const char* ssid, const char* password) {
Serial.print("Connecting to ");
Serial.println(_SSID);
Serial.println(ssid);

int attempts = 0;
while (WL_CONNECTED != WiFi.status()) { // try to connect to the network
attempts++;
Serial.println("Atempt to connect: " + String(attempts));
WiFi.begin(_SSID.c_str(), _SSIDPassword.c_str());
Serial.println("Attempt to connect: " + String(attempts));
WiFi.begin(ssid, password);
for (int i = 0; i < 50; i++) { // wait for connection
Serial.print(".");
delay(500);
Expand All @@ -77,30 +78,33 @@ void RemoteControl::_setupNetwork() {
Serial.println("Connection status: " + String(WiFi.status()));
}
Serial.println();

// successfully connected
debugNetworkInfo();
}

void RemoteControl::setup(RemoteControlRoutesCallback routes) {
_setupNetwork();
routes(_app); // setup routes
void RemoteControl::setup() { reconnect(); }

void RemoteControl::reconnect() {
// reset everything
WiFi.disconnect();
verifyNetwork();
_app = Application(); // reset routes
_server = WiFiServer(80); // reset server
// reconnect
_onConnect(*this, _app);
_server.begin();
}

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

if (client.connected()) {
_app.process(&client);
client.stop();
}
}

String RemoteControl::asJSONString() const {
String result = "{";
result += "\"SSID\": \"" + _SSID + "\",";
result += "\"signal strength\": " + String(WiFi.RSSI());
result += "}";
return result;
}
19 changes: 12 additions & 7 deletions controller/tea_poor/lib/Arduino/RemoteControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,25 @@
#include <Arduino.h>
#include <WiFiS3.h>
#include <aWOT.h>
#include <functional>

// define routes callback function signature
typedef void (*RemoteControlRoutesCallback)(Application &app);
// forward declaration
class RemoteControl;

// define callback for (re)connecting to WiFi, use std::function
typedef std::function<void(RemoteControl&, Application&)> NetworkConnectCallback;

class RemoteControl {
public:
RemoteControl(const char* SSID, const char* SSIDPassword);
RemoteControl(const NetworkConnectCallback &onConnect);
~RemoteControl();
void setup(RemoteControlRoutesCallback routes);
void setup();
void process();
String asJSONString() const;
void reconnect();
///////////////////
void connectTo(const char* ssid, const char* password);
private:
const String _SSID;
const String _SSIDPassword;
NetworkConnectCallback _onConnect;
WiFiServer _server;
Application _app;

Expand Down
4 changes: 3 additions & 1 deletion controller/tea_poor/lib/Arduino/WaterPumpController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ WaterPumpController::WaterPumpController(int directionPin, int brakePin, int pow
WaterPumpController::~WaterPumpController() {}

void WaterPumpController::setup() {
pinMode(_directionPin, OUTPUT);
// NOTE: we use one-directional motor, so we can't use direction pin
// but I keep it here for future reference
// pinMode(_directionPin, OUTPUT);
pinMode(_brakePin, OUTPUT);
pinMode(_powerPin, OUTPUT);
stop();
Expand Down
6 changes: 4 additions & 2 deletions controller/tea_poor/lib/CommandProcessor/CommandProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ bool isValidIntNumber(const char *str, const int maxValue, const int minValue=0)

std::string CommandProcessor::status() {
std::stringstream response;
const auto now = _env->time();
response << "{";
// send current time in milliseconds to synchronize time on client side
response << "\"time\": " << now << ", ";
// 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\": {"
Expand All @@ -43,7 +45,7 @@ std::string CommandProcessor::status() {
}

std::string CommandProcessor::pour_tea(const char *milliseconds) {
if (!isValidIntNumber(milliseconds, _waterPumpSafeThreshold)) {
if (!isValidIntNumber(milliseconds, _waterPumpSafeThreshold + 1)) {
// send error message as JSON
return std::string("{ \"error\": \"invalid milliseconds value\" }");
}
Expand Down
26 changes: 18 additions & 8 deletions controller/tea_poor/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ auto waterPump = std::make_shared<WaterPumpScheduler>(
)
);

// setting up remote control
RemoteControl remoteControl(WIFI_SSID, WIFI_PASSWORD);

// build command processor
CommandProcessor commandProcessor(
WATER_PUMP_SAFE_THRESHOLD,
Expand All @@ -35,10 +32,17 @@ void withExtraHeaders(Response &res) {
res.set("Content-Type", "application/json");
}

void setup() {
Serial.begin(9600);
waterPump->setup();
remoteControl.setup([](Application &app) {
RemoteControl remoteControl(
// lambda function to setup network
[](RemoteControl &remoteControl, Application &app) {
// connect to WiFi
// set static IP address, if defined in configs
#ifdef WIFI_IP_ADDRESS
WiFi.config(WIFI_IP_ADDRESS);
#endif

remoteControl.connectTo(WIFI_SSID, WIFI_PASSWORD);
// setup routes
app.get("/pour_tea", [](Request &req, Response &res) {
char milliseconds[64];
req.query("milliseconds", milliseconds, 64);
Expand All @@ -59,7 +63,13 @@ void setup() {
withExtraHeaders(res);
res.print(response.c_str());
});
});
}
);

void setup() {
Serial.begin(9600);
waterPump->setup();
remoteControl.setup();
}

void loop() {
Expand Down
3 changes: 3 additions & 0 deletions controller/tea_poor/src/secrets.h.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,7 @@ const int WATER_PUMP_POWER_PIN = 3;
// Their is no reason to make it configurable and add unnecessary complexity
const int WATER_PUMP_SAFE_THRESHOLD = 10 * 1000;

// Static IP address. If not defined, dynamic IP address will be used
// #define WIFI_IP_ADDRESS IPAddress(192, 168, 1, 123)

#endif // SECRETS_H
23 changes: 16 additions & 7 deletions controller/tea_poor/test/test_native/tests/CommandProcessor_test.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,28 @@
#include "mocks/FakeWaterPumpSchedulerAPI.h"
#include "mocks/FakeEnvironment.h"

const auto INVALID_TIME_ERROR_MESSAGE = "{ \"error\": \"invalid milliseconds 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) {
const auto EXPECTED_ERROR_MESSAGE = "{ \"error\": \"invalid milliseconds value\" }";
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);
}

// 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>();
CommandProcessor commandProcessor(123, env, waterPump);

// 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);
}
ASSERT_NE(commandProcessor.pour_tea("0"), INVALID_TIME_ERROR_MESSAGE);
ASSERT_NE(commandProcessor.pour_tea("123"), INVALID_TIME_ERROR_MESSAGE);
}

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

const auto response = commandProcessor.status();
ASSERT_EQ(response, "{"
"\"time\": 123, "
"\"water threshold\": 12345, "
"\"pump\": {"
" \"running\": true, "
Expand Down
Binary file added ui/public/valve.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions ui/src/App.css
Original file line number Diff line number Diff line change
@@ -1,2 +1,15 @@
.App {
}

.countdown-area {
width: 100%;
text-align: center;
font-weight: bold;
font-size: 2rem;
}

.hold-to-pour-image {
object-fit: contain;
width: 25%;
height: auto;
}
13 changes: 8 additions & 5 deletions ui/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import { Container, Form } from 'react-bootstrap';
import { connect } from 'react-redux';

import NotificationsArea from './components/NotificationsArea.js';
import APIAddressField from './components/APIAddressField';
import PourTimeField from './components/PourTimeField';
import SystemControls from './components/SystemControls';
import SystemStatusArea from './components/SystemStatusArea';
import APIAddressField from './components/APIAddressField.js';
import PourTimeField from './components/PourTimeField.js';
import SystemControls from './components/SystemControls.js';
import SystemStatusArea from './components/SystemStatusArea.js';
import CurrentOperationInfoArea from './components/CurrentOperationInfoArea.js';
import HoldToPour from './components/HoldToPour.js';

function App({ isConnected }) {
// TODO: Add a fake countdown timer of timeLeft
return (
<Container className="App">
<h1>Tea System UI</h1>
Expand All @@ -21,7 +22,9 @@ function App({ isConnected }) {
{isConnected ? (
<>
<PourTimeField />
<CurrentOperationInfoArea />
<SystemControls />
<HoldToPour />
</>
) : null}
</Form>
Expand Down
6 changes: 0 additions & 6 deletions ui/src/App.test.js

This file was deleted.

22 changes: 22 additions & 0 deletions ui/src/Utils/time.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
function toTimeStr(diff) {
const seconds = Math.floor(diff / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);

const secondsStr = (seconds % 60).toString().padStart(2, '0');
const minutesStr = (minutes % 60).toString().padStart(2, '0');
const hoursStr = hours.toString().padStart(2, '0');

return `${hoursStr}:${minutesStr}:${secondsStr}`;
}

export function timeBetweenAsString({endTime=null, startTime=null, bounded=false}) {
if (null === startTime) startTime = new Date();
if (null === endTime) endTime = new Date();

let diff = endTime - startTime; // in ms
if (bounded && (diff < 0)) diff = 0;

if (diff < 0) return '-' + toTimeStr(-diff);
return toTimeStr(diff);
}
Loading

0 comments on commit 3f1d094

Please sign in to comment.