Refactor Overview page, make Settings page to home and add warning when temp or soil values are critical.

This commit is contained in:
Andrés Uribe Stengel 2020-07-23 17:59:36 +02:00
parent 52a5752db7
commit 8da0b54d2b
7 changed files with 250 additions and 160 deletions

View File

@ -1,6 +1,6 @@
import {SensorDataCollection} from "../../client/main";
export function getAllEspNames() {
export function getAllEspIds() {
return _.uniq(SensorDataCollection.find({}, {
sort: {device_id: 1}, fields: {device_id: true}
}).fetch().map(function (x) {

View File

@ -2,7 +2,6 @@ import React, { Suspense } from 'react'
import NavBar from './NavigationBar'
import Home from './Home'
import Overview from './Overview'
import Settings from './Settings'
import About from './About'
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
@ -20,9 +19,6 @@ function App() {
<Route path="/overview">
<div className="p-3"><Overview></Overview></div>
</Route>
<Route path="/settings">
<div className="p-3"><Settings></Settings></div>
</Route>
<Route path="/about" component={About}></Route>
</Switch>
</Suspense>

View File

@ -1,11 +1,13 @@
import React from 'react'
import {CartesianGrid, Legend, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis} from 'recharts';
import SensorCardDeck from './SensorCardDeck'
import {SensorDataCollection, ActiveDeviceCollection} from "../../client/main";
import {
SensorDataCollection,
ActiveDeviceCollection,
ConfiguredDevicesCollection,
PlantTypesCollection
} from "../../client/main";
import {useTracker} from 'meteor/react-meteor-data';
import { Col, Form, Row, Card, CardDeck } from "react-bootstrap";
import { getAllEspNames } from "../api/espNames";
import moment from 'moment'
import { Card, CardDeck, Table } from "react-bootstrap";
import Settings from "./Settings";
export default function Home() {
const deviceId = useTracker(() => {
@ -23,19 +25,36 @@ export default function Home() {
}
});
const handleChange = (e) => {
if (e.target.value === "") {
console.log("No device selected!");
} else {
var doc = ActiveDeviceCollection.findOne({deviceId: deviceId.deviceId});
ActiveDeviceCollection.update({_id: doc._id}, {$set: {deviceId: e.target.value}});
}
const configuredDevices = useTracker(() => {
return ConfiguredDevicesCollection.find().fetch();
});
function getSoil(type) {
const plantType = PlantTypesCollection.findOne({plantType: type});
return plantType.dirtType
}
const getLabelFromStamp = (x) => {
return moment(x.timestamp).format("HH:mm");
function getPermWilPoint(type) {
const plantType = PlantTypesCollection.findOne({plantType: type});
return plantType.data.soilMoisture.pwp
}
function getFieldCap(type) {
const plantType = PlantTypesCollection.findOne({plantType: type});
return plantType.data.soilMoisture.fc
}
function getMinTemp(type) {
const plantType = PlantTypesCollection.findOne({plantType: type});
return plantType.data.temperature.minTemp
}
function getMinLux(type) {
const plantType = PlantTypesCollection.findOne({plantType: type});
return plantType.data.light.minLux
}
if ((sensorData.length <= 0)) {
return (
<CardDeck>
@ -50,84 +69,41 @@ export default function Home() {
} else {
return (
<>
<Table striped bordered hover responsive>
<thead>
<tr key={'head'}>
<th key={'#'}>#</th>
<th key={'nickname'}>Device Nickname</th>
<th key={'id'}>Device ID</th>
<th key={'type'}>Plant Type</th>
<th key={'soil'}>Soil Type</th>
<th key={'pwp'}>Permanent Wilting Point</th>
<th key={'fc'}>Field Capacity</th>
<th key={'temp'}>Min Temperature</th>
<th key={'brightness'}>Min Brightness</th>
</tr>
</thead>
<tbody>
{configuredDevices.map((device, index) => {
return <tr key={'body' + index}>
<td key={'#-' + index}>{index}</td>
<td key={'nickname-' + index}>{device.alias}</td>
<td key={'id-' + index}>{device.deviceId}</td>
<td key={'type-' + index}>{device.type}</td>
<td key={'soil-' + index}>{getSoil(device.type)}</td>
<td key={'pwp-' + index}>{getPermWilPoint(device.type) + " %"}</td>
<td key={'fc-' + index}>{getFieldCap(device.type) + " %"}</td>
<td key={'temp-' + index}>{getMinTemp(device.type) + " °C"}</td>
<td key={'brightness-' + index}>{getMinLux(device.type) + " lux"}</td>
</tr>
})}
</tbody>
</Table>
<br></br>
<Row>
<Col xs lg="2">
<h4>Devices:</h4>
</Col>
</Row>
<Row>
<Col xs lg="2">
<Form>
<Form.Group>
<Form.Control as="select" type="text" onChange={handleChange}>
<option></option>
{getAllEspNames().map((espName, index) => {
return <option key={index} value={espName}>{espName}</option>
})}
</Form.Control>
</Form.Group>
</Form>
</Col>
<Col>
<h6>active device: {deviceId.deviceId}</h6>
</Col>
</Row>
<SensorCardDeck/>
<Row>
<Col>
<ResponsiveContainer width='100%' height={325}>
<LineChart data={sensorData.filter(el => el.temperature !== null)} margin={{top: 50, right: 50, bottom: 20, left: 5}}>
<Line type="monotone" dataKey="temperature" stroke="#10b5de"/>
<CartesianGrid stroke="#ccc" strokeDasharray="5 5"/>
<XAxis dataKey={getLabelFromStamp}/>
<YAxis/>
<Tooltip/>
<Legend/>
</LineChart>
</ResponsiveContainer>
</Col>
<Col>
<ResponsiveContainer width='100%' height={325}>
<LineChart data={sensorData.filter(el => el.humidity !== null)} margin={{top: 50, right: 50, bottom: 20, left: 5}}>
<Line type="monotone" dataKey="humidity" stroke="#ff6f00"/>
<CartesianGrid stroke="#ccc" strokeDasharray="5 5"/>
<XAxis dataKey={getLabelFromStamp}/>
<YAxis/>
<Tooltip/>
<Legend/>
</LineChart>
</ResponsiveContainer>
</Col>
</Row>
<Row>
<Col>
<ResponsiveContainer width='100%' height={325}>
<LineChart data={sensorData.filter(el => el.brightness !== null)} margin={{top: 50, right: 50, bottom: 20, left: 5}}>
<Line type="monotone" dataKey="brightness" stroke="#ffd500"/>
<CartesianGrid stroke="#ccc" strokeDasharray="5 5"/>
<XAxis dataKey={getLabelFromStamp}/>
<YAxis/>
<Tooltip/>
<Legend/>
</LineChart>
</ResponsiveContainer>
</Col>
<Col>
<ResponsiveContainer width='100%' height={325}>
<LineChart data={sensorData.filter(el => el.moisture !== null)} margin={{top: 50, right: 50, bottom: 20, left: 5}}>
<Line type="monotone" dataKey="moisture" stroke="#1c4399"/>
<CartesianGrid stroke="#ccc" strokeDasharray="5 5"/>
<XAxis dataKey={getLabelFromStamp}/>
<YAxis/>
<Tooltip/>
<Legend/>
</LineChart>
</ResponsiveContainer>
</Col>
</Row>
<h4>If your device is not visible in the table above, configure it here below.</h4>
<br></br>
<br></br>
<Settings></Settings>
</>
)
}

View File

@ -24,7 +24,6 @@ export default function NavigationBar() {
<Navbar.Collapse id="responsive-navbar-nav">
<Nav className="mr-auto">
<Nav.Link href="/overview">Overview</Nav.Link>
<Nav.Link href="/settings">Settings</Nav.Link>
<Nav.Link href="/about">About</Nav.Link>
{
/*

View File

@ -1,23 +1,21 @@
import React from 'react'
import {
SensorDataCollection,
ActiveDeviceCollection,
ConfiguredDevicesCollection,
PlantTypesCollection
} from "../../client/main";
import {CartesianGrid, Legend, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis} from 'recharts';
import SensorCardDeck from './SensorCardDeck'
import {SensorDataCollection, ActiveDeviceCollection, ConfiguredDevicesCollection} from "../../client/main";
import {useTracker} from 'meteor/react-meteor-data';
import { Card, CardDeck, Table } from "react-bootstrap";
import { Col, Form, Row, Card, CardDeck } from "react-bootstrap";
import moment from 'moment'
export default function Overview() {
const deviceId = useTracker(() => {
const activeDevice = useTracker(() => {
return ActiveDeviceCollection.find().fetch()[0];
});
const sensorData = useTracker(() => {
if (deviceId === null || deviceId === undefined) {
if (activeDevice === null || activeDevice === undefined) {
return [];
} else {
return SensorDataCollection.find({device_id: deviceId.deviceId}, {
return SensorDataCollection.find({device_id: activeDevice.deviceId}, {
sort: {timestamp: -1},
limit: 61
}).fetch().reverse();
@ -28,11 +26,18 @@ export default function Overview() {
return ConfiguredDevicesCollection.find().fetch();
});
function getDirt(type) {
const plantType = PlantTypesCollection.findOne({plantType: type});
return plantType.dirtType
const handleChange = (e) => {
if (e.target.value === "") {
console.log("No device selected!");
} else {
var doc = ActiveDeviceCollection.findOne({deviceId: activeDevice.deviceId});
ActiveDeviceCollection.update({_id: doc._id}, {$set: {deviceId: e.target.value}});
}
}
const getLabelFromStamp = (x) => {
return moment(x.timestamp).format("HH:mm");
}
if ((sensorData.length <= 0)) {
return (
@ -48,28 +53,84 @@ export default function Overview() {
} else {
return (
<>
<Table striped bordered hover responsive>
<thead>
<tr key={'head'}>
<th key={'#'}>#</th>
<th key={'name'}>Device Name</th>
<th key={'type'}>Plant Type</th>
<th key={'dirt'}>Dirt Type</th>
</tr>
</thead>
<tbody>
{configuredDevices.map((device, index) => {
return <tr key={'body' + index}>
<td key={'#-' + index}>{index}</td>
<td key={'name-' + index}>{device.deviceId}</td>
<td key={'type-' + index}>{device.type}</td>
<td key={'dirt-' + index}>{getDirt(device.type)}</td>
</tr>
})}
</tbody>
</Table>
<br></br>
<h4>If your device is not visible, go to Settings to configure it first.</h4>
<Row>
<Col xs lg="2">
<h4>Devices:</h4>
</Col>
</Row>
<Row>
<Col xs lg="2">
<Form>
<Form.Group>
<Form.Control as="select" type="text" onChange={handleChange}>
<option></option>
{configuredDevices.map((devices, index) => {
return <option key={index} value={devices.deviceId}>{devices.alias}</option>
})}
</Form.Control>
</Form.Group>
</Form>
</Col>
<Col>
<h6>active device: {ConfiguredDevicesCollection.findOne({deviceId: activeDevice.deviceId}).alias}</h6>
</Col>
</Row>
<SensorCardDeck/>
<Row>
<Col>
<ResponsiveContainer width='100%' height={325}>
<LineChart data={sensorData.filter(el => el.temperature !== null)} margin={{top: 50, right: 50, bottom: 20, left: 5}}>
<Line type="monotone" dataKey="temperature" stroke="#10b5de"/>
<CartesianGrid stroke="#ccc" strokeDasharray="5 5"/>
<XAxis dataKey={getLabelFromStamp}/>
<YAxis/>
<Tooltip/>
<Legend/>
</LineChart>
</ResponsiveContainer>
</Col>
<Col>
<ResponsiveContainer width='100%' height={325}>
<LineChart data={sensorData.filter(el => el.humidity !== null)} margin={{top: 50, right: 50, bottom: 20, left: 5}}>
<Line type="monotone" dataKey="humidity" stroke="#ff6f00"/>
<CartesianGrid stroke="#ccc" strokeDasharray="5 5"/>
<XAxis dataKey={getLabelFromStamp}/>
<YAxis/>
<Tooltip/>
<Legend/>
</LineChart>
</ResponsiveContainer>
</Col>
</Row>
<Row>
<Col>
<ResponsiveContainer width='100%' height={325}>
<LineChart data={sensorData.filter(el => el.brightness !== null)} margin={{top: 50, right: 50, bottom: 20, left: 5}}>
<Line type="monotone" dataKey="brightness" stroke="#ffd500"/>
<CartesianGrid stroke="#ccc" strokeDasharray="5 5"/>
<XAxis dataKey={getLabelFromStamp}/>
<YAxis/>
<Tooltip/>
<Legend/>
</LineChart>
</ResponsiveContainer>
</Col>
<Col>
<ResponsiveContainer width='100%' height={325}>
<LineChart data={sensorData.filter(el => el.moisture !== null)} margin={{top: 50, right: 50, bottom: 20, left: 5}}>
<Line type="monotone" dataKey="moisture" stroke="#1c4399"/>
<CartesianGrid stroke="#ccc" strokeDasharray="5 5"/>
<XAxis dataKey={getLabelFromStamp}/>
<YAxis/>
<Tooltip/>
<Legend/>
</LineChart>
</ResponsiveContainer>
</Col>
</Row>
</>
)
}

View File

@ -1,6 +1,11 @@
import React from 'react'
import { Card, CardDeck } from 'react-bootstrap';
import {ActiveDeviceCollection, SensorDataCollection} from "../../client/main";
import {
ActiveDeviceCollection,
ConfiguredDevicesCollection,
PlantTypesCollection,
SensorDataCollection
} from "../../client/main";
import {useTracker} from 'meteor/react-meteor-data';
export default function SensorCardDeck() {
@ -12,6 +17,14 @@ export default function SensorCardDeck() {
return SensorDataCollection.find({ device_id: deviceId.deviceId }, { sort: { timestamp: -1 }, limit: 1 }).fetch();
});
const plantType = useTracker(() => {
return ConfiguredDevicesCollection.findOne({ deviceId: deviceId.deviceId }).type;
});
const plantTypeData = useTracker(() => {
return PlantTypesCollection.findOne({ plantType: plantType });
});
if (sensorData.length <= 0) {
return (
@ -27,12 +40,19 @@ export default function SensorCardDeck() {
} else {
return (
<CardDeck>
<Card>
<Card.Body>
<Card.Title>Temperature</Card.Title>
<Card.Text>{sensorData[0].temperature} °C</Card.Text>
</Card.Body>
</Card>
{sensorData[0].temperature < plantTypeData.data.temperature.minTemp ?
<Card border={'danger'}>
<Card.Body>
<Card.Title>Temperature</Card.Title>
<Card.Text>{sensorData[0].temperature} °C</Card.Text>
</Card.Body>
</Card> :
<Card>
<Card.Body>
<Card.Title>Temperature</Card.Title>
<Card.Text>{sensorData[0].temperature} °C</Card.Text>
</Card.Body>
</Card>}
<Card>
<Card.Body>
<Card.Title>Humidity</Card.Title>
@ -45,12 +65,19 @@ export default function SensorCardDeck() {
<Card.Text>{sensorData[0].brightness} lux</Card.Text>
</Card.Body>
</Card>
<Card>
<Card.Body>
<Card.Title>Moisture</Card.Title>
<Card.Text>{sensorData[0].moisture} %</Card.Text>
</Card.Body>
</Card>
{sensorData[0].moisture > plantTypeData.data.soilMoisture.sat ?
<Card border={'danger'}>
<Card.Body>
<Card.Title>Moisture</Card.Title>
<Card.Text>{sensorData[0].moisture} %</Card.Text>
</Card.Body>
</Card> :
<Card>
<Card.Body>
<Card.Title>Moisture</Card.Title>
<Card.Text>{sensorData[0].moisture} %</Card.Text>
</Card.Body>
</Card>}
</CardDeck>
)
}

View File

@ -1,7 +1,7 @@
import React from 'react'
import {Col, Form, Row, Card, CardDeck, Button, Container} from "react-bootstrap";
import { Meteor } from 'meteor/meteor';
import {getAllEspNames} from "../api/espNames";
import {getAllEspIds} from "../api/espIds";
import {getAllPlantTypes} from "../api/plantTypes";
import {ConfiguredDevicesCollection, PlantTypesCollection, ActiveDeviceCollection, SensorDataCollection} from "../../client/main";
import {useTracker} from 'meteor/react-meteor-data';
@ -27,18 +27,27 @@ export default function Settings() {
return PlantTypesCollection.find();
});
var selectedEspName;
var selectedEspId;
var selectedAlias = "";
var selectedType;
const payloadVegi = plantTypes.fetch()[0];
const payloadCacti = plantTypes.fetch()[1];
const payloadFlower = plantTypes.fetch()[2];
const handleChangeName = (e) => {
const handleChangeAlias = (e) => {
if (e.target.value === "") {
console.log("Nickname cannot be empty!");
} else {
selectedAlias = e.target.value;
}
}
const handleChangeDevice = (e) => {
if (e.target.value === "") {
console.log("No device selected!");
} else {
selectedEspName = e.target.value;
selectedEspId = e.target.value;
}
}
@ -50,16 +59,35 @@ export default function Settings() {
}
}
const handleTest = (e) => {
const handleSendData = (e) => {
var payload = "";
if (selectedType === "Vegetables") {payload = JSON.stringify(payloadVegi.data.soilMoisture);}
if (selectedType === "Cacti") {payload = JSON.stringify(payloadCacti.data.soilMoisture);}
if (selectedType === "Flowers") {payload = JSON.stringify(payloadFlower.data.soilMoisture);}
if ((payload === "") || (selectedEspName === undefined) || (selectedType === undefined)) {alert("No device or type selected!");} else {
var doc = ConfiguredDevicesCollection.findOne({deviceId: selectedEspId});
if (doc === undefined) {ConfiguredDevicesCollection.insert({deviceId: selectedEspId, alias: selectedEspId, type: selectedType});} else {
ConfiguredDevicesCollection.update({_id: doc._id}, {$set: {alias: selectedAlias, type: selectedType}});
}
if ((payload === "") || (selectedEspId === undefined) || (selectedAlias === "") || (selectedType === undefined)) {alert("Nickname is empty or no device/type selected!");} else {
Meteor.call('mqttPublish', {
topic: 'smartgarden/commands/' + selectedEspName + '/soil',
topic: 'smartgarden/commands/' + selectedEspId + '/soil',
payload: payload
}, (err, res) => {
if (err) {
alert(err);
} else {
console.log('success')
}
})
Meteor.call('mqttPublish', {
topic: 'smartgarden/commands/' + selectedEspId + '/automatic',
payload: JSON.stringify({
'irrigation': "true",
'light': 'true'
})
}, (err, res) => {
if (err) {
alert(err);
@ -67,11 +95,6 @@ export default function Settings() {
alert('success')
}
})
var doc = ConfiguredDevicesCollection.findOne({deviceId: selectedEspName});
if (doc === undefined) {ConfiguredDevicesCollection.insert({deviceId: selectedEspName, type: selectedType});} else {
ConfiguredDevicesCollection.update({_id: doc._id}, {$set: {type: selectedType}});
}
}
}
@ -105,9 +128,17 @@ export default function Settings() {
<Form>
<Form.Group>
<Form.Label>Name:</Form.Label>
<Form.Control as="select" type="text" onChange={handleChangeName}>
<Form.Control
type="text"
id="alias"
placeholder="Enter a device nickname..."
onChange={handleChangeAlias}/>
</Form.Group>
<Form.Group>
<Form.Label>Device:</Form.Label>
<Form.Control as="select" type="text" onChange={handleChangeDevice}>
<option></option>
{getAllEspNames().map((espName, index) => {
{getAllEspIds().map((espName, index) => {
return <option key={index} value={espName}>{espName}</option>
})}
</Form.Control>
@ -121,7 +152,7 @@ export default function Settings() {
})}
</Form.Control>
</Form.Group>
<Button type={"test"} onClick={handleTest} >Send</Button>
<Button type={"sendData"} onClick={handleSendData} >Send</Button>
</Form>
</Container>