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

Feature/UI/countdown #6

Merged
merged 16 commits into from
Jan 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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