diff --git a/client/main.jsx b/client/main.jsx
index cda4586..a68555b 100644
--- a/client/main.jsx
+++ b/client/main.jsx
@@ -3,6 +3,8 @@ import { Meteor } from 'meteor/meteor';
import ReactDOM from 'react-dom';
import App from '../imports/ui/App';
import 'bootstrap/dist/css/bootstrap.min.css';
+import startMqttObserver from "../mongo-mqtt";
+import {publish} from "../imports/api/mqttApi";
export const PlantTypesCollection = new Meteor.Collection('plantTypes');
export const SensorDataCollection = new Meteor.Collection('sensorData');
@@ -10,6 +12,8 @@ export const ActiveDeviceCollection = new Meteor.Collection('activeDevice');
export const ConfiguredDevicesCollection = new Meteor.Collection('configuredDevices');
Meteor.startup(() => {
+ // If this code is on the meteor server-side, publish all MongoDB collections for the client-side,
+ // start the mqtt-observer script to push data to the database and define the mqttPublish method to be accessible from the client-side.
if(Meteor.isServer) {
Meteor.publish('plantTypesCollection', function() {
return PlantTypesCollection.find();
@@ -26,8 +30,17 @@ Meteor.startup(() => {
Meteor.publish('configuredDevicesCollection', function() {
return ConfiguredDevicesCollection.find();
})
+
+ startMqttObserver()
+
+ Meteor.methods({
+ 'mqttPublish'({ topic, payload }) {
+ publish(topic, payload)
+ }
+ })
}
+ // If this code is on the meteor client-side, subscribe all published MongoDB collections from the server-side and render the application.
if (Meteor.isClient) {
Meteor.subscribe('plantTypesCollection');
Meteor.subscribe('sensorDataCollection');
diff --git a/imports/api/espIds.js b/imports/api/espIds.js
index 07360fd..7b277d7 100644
--- a/imports/api/espIds.js
+++ b/imports/api/espIds.js
@@ -1,5 +1,7 @@
import {SensorDataCollection} from "../../client/main";
+// Fetch all esp-IDs stored in all documents of the sensorData collection and remove duplicates,
+// returning an array of esp-IDs that have sent data to the database.
export function getAllEspIds() {
return _.uniq(SensorDataCollection.find({}, {
sort: {device_id: 1}, fields: {device_id: true}
diff --git a/imports/api/mqttApi.js b/imports/api/mqttApi.js
index 5b400aa..5d6703c 100644
--- a/imports/api/mqttApi.js
+++ b/imports/api/mqttApi.js
@@ -2,6 +2,7 @@ import { connect } from 'mqtt';
var client = connect('mqtt://mqtt.timovolkmann.de')
+// If connection to the mqtt-client is not established, try to reconnect. Else publish a message to the mqtt broker.
function publish(topic, message) {
if (!client.connected) {
client.reconnect();
diff --git a/imports/api/plantTypes.js b/imports/api/plantTypes.js
index d8af609..7c7606c 100644
--- a/imports/api/plantTypes.js
+++ b/imports/api/plantTypes.js
@@ -1,5 +1,6 @@
import {PlantTypesCollection} from '../../client/main'
+// Return an array of all plant types currently stored in the database.
export function getAllPlantTypes() {
const plantTypesDocuments = PlantTypesCollection.find();
diff --git a/imports/ui/Home.jsx b/imports/ui/Home.jsx
index 3a4934f..6c69d2a 100644
--- a/imports/ui/Home.jsx
+++ b/imports/ui/Home.jsx
@@ -5,30 +5,42 @@ import { Table } from "react-bootstrap";
import Settings from "./Settings";
export default function Home() {
+ // Return all documents of the configuredDevices collection.
const configuredDevices = useTracker(() => {
return ConfiguredDevicesCollection.find().fetch();
});
+ // Return the soil type of the given plant type.
function getSoil(type) {
const plantType = PlantTypesCollection.findOne({plantType: type});
return plantType.dirtType
}
+ // Return the permanent wilting point of the given plant type.
function getPermWilPoint(type) {
const plantType = PlantTypesCollection.findOne({plantType: type});
return plantType.data.soilMoisture.pwp
}
+ // Return the field capacity of the given plant type.
function getFieldCap(type) {
const plantType = PlantTypesCollection.findOne({plantType: type});
return plantType.data.soilMoisture.fc
}
+ // Return the minimal temperature of the given plant type.
function getMinTemp(type) {
const plantType = PlantTypesCollection.findOne({plantType: type});
return plantType.data.temperature.minTemp
}
+ // Return the maximal temperature of the given plant type.
+ function getMaxTemp(type) {
+ const plantType = PlantTypesCollection.findOne({plantType: type});
+ return plantType.data.temperature.maxTemp
+ }
+
+ // Return the minimal lux value of the given plant type.
function getMinLux(type) {
const plantType = PlantTypesCollection.findOne({plantType: type});
return plantType.data.light.minLux
@@ -48,7 +60,8 @@ export default function Home() {
Mode |
Permanent Wilting Point |
Field Capacity |
- Min Temperature |
+ Min Temperature |
+ Max Temperature |
Min Brightness |
@@ -63,7 +76,8 @@ export default function Home() {
{device.mode} |
{getPermWilPoint(device.type) + " %"} |
{getFieldCap(device.type) + " %"} |
- {getMinTemp(device.type) + " °C"} |
+ {getMinTemp(device.type) + " °C"} |
+ {getMaxTemp(device.type) + " °C"} |
{getMinLux(device.type) + " lux"} |
})}
diff --git a/imports/ui/Overview.jsx b/imports/ui/Overview.jsx
index 924bc92..9f7b0f5 100644
--- a/imports/ui/Overview.jsx
+++ b/imports/ui/Overview.jsx
@@ -5,18 +5,20 @@ import {SensorDataCollection, ActiveDeviceCollection, ConfiguredDevicesCollectio
import {useTracker} from 'meteor/react-meteor-data';
import { Col, Form, Row, Card, CardDeck } from "react-bootstrap";
import moment from 'moment'
-import { scaleLog } from 'd3-scale';
-const scale = scaleLog().base(Math.E);
export default function Overview() {
+ // Return the document of the currently active device in the activeDevice collection.
const activeDevice = useTracker(() => {
return ActiveDeviceCollection.find().fetch()[0];
});
+ // Return all documents of the configuredDevices collection.
const configuredDevices = useTracker(() => {
return ConfiguredDevicesCollection.find().fetch();
});
+ // If the activeDevice is not null or undefined, return and filter the documents of the last two days (48 Hrs) of the currently active device in the sensorData collection
+ // and reverse the array so that the oldest documents come first.
const sensorData = useTracker(() => {
if (activeDevice === null || activeDevice === undefined) {
return [];
@@ -33,6 +35,7 @@ export default function Overview() {
}
});
+ // Set the selected device from the configured devices dropdown-menu, to the new currently active device.
const handleChange = (e) => {
if (e.target.value === "") {
console.log("No device selected!");
@@ -42,11 +45,12 @@ export default function Overview() {
}
}
+ // Get the current date as string.
const getLabelFromStamp = (x) => {
return moment(x.timestamp).format("LLLL");
- // return moment(x.timestamp).format("HH:mm");
}
+ // Workaround for log(0).
const brightnessMapper = (x) => {
if (x.brightness < 1) {
x.brightness = 0.1
@@ -54,6 +58,7 @@ export default function Overview() {
return x;
}
+ //If the data that is loaded from the database has not been fetched yet, show a loading screen. Else load the application.
if ((sensorData.length <= 0)) {
return (
<>
@@ -77,7 +82,8 @@ export default function Overview() {
- active device: {!activeDevice === undefined && !ConfiguredDevicesCollection.findOne({ deviceId: activeDevice.deviceId }) === undefined ? ConfiguredDevicesCollection.findOne({ deviceId: activeDevice.deviceId }).alias : "No device selected"}
+ active device: {!activeDevice === undefined && !ConfiguredDevicesCollection.findOne({ deviceId: activeDevice.deviceId }) === undefined ?
+ ConfiguredDevicesCollection.findOne({ deviceId: activeDevice.deviceId }).alias : "No device selected"}
@@ -114,7 +120,8 @@ export default function Overview() {
- active device: {ConfiguredDevicesCollection.findOne({deviceId: activeDevice.deviceId}) === undefined ? "No device selected" : ConfiguredDevicesCollection.findOne({deviceId: activeDevice.deviceId}).alias}
+ active device: {ConfiguredDevicesCollection.findOne({deviceId: activeDevice.deviceId}) === undefined ?
+ "No device selected" : ConfiguredDevicesCollection.findOne({deviceId: activeDevice.deviceId}).alias}
diff --git a/imports/ui/SensorCardDeck.jsx b/imports/ui/SensorCardDeck.jsx
index f281d70..a33917e 100644
--- a/imports/ui/SensorCardDeck.jsx
+++ b/imports/ui/SensorCardDeck.jsx
@@ -9,10 +9,12 @@ import {
import {useTracker} from 'meteor/react-meteor-data';
export default function SensorCardDeck() {
+ // Return the document of the currently active device in the activeDevice collection.
const deviceId = useTracker(() => {
return ActiveDeviceCollection.find().fetch()[0];
});
+ // If the deviceId is not null or undefined, return the most recent document in the sensorData collection of the currently active device.
const sensorData = useTracker(() => {
if (deviceId === null || deviceId === undefined) {
return undefined;
@@ -21,7 +23,8 @@ export default function SensorCardDeck() {
}
});
- const plantType = useTracker(() => {
+ // If the deviceId is not null or undefined, return the document in the configuredDevices collection of the currently active device.
+ const configurationData = useTracker(() => {
if (deviceId === null || deviceId === undefined) {
return undefined;
} else {
@@ -29,16 +32,18 @@ export default function SensorCardDeck() {
}
});
+ // If the configurationData is not null or undefined, return the configured plant type of the currently active device.
const plantTypeData = useTracker(() => {
- if (plantType === null || plantType === undefined) {
+ if (configurationData === null || configurationData === undefined) {
return undefined;
} else {
- return PlantTypesCollection.findOne({plantType: plantType.type});
+ return PlantTypesCollection.findOne({plantType: configurationData.type});
}
});
- if (sensorData.length <= 0 || plantType === undefined || deviceId === undefined || plantTypeData === undefined) {
+ //If the data that is loaded from the database has not been fetched yet, show a loading screen. Else load the application.
+ if (sensorData.length <= 0 || configurationData === undefined || deviceId === undefined || plantTypeData === undefined) {
return (
diff --git a/imports/ui/Settings.jsx b/imports/ui/Settings.jsx
index a40855f..77602e2 100644
--- a/imports/ui/Settings.jsx
+++ b/imports/ui/Settings.jsx
@@ -7,21 +7,25 @@ import {ConfiguredDevicesCollection, PlantTypesCollection} from "../../client/ma
import {useTracker} from 'meteor/react-meteor-data';
export default function Settings() {
+ // Return all documents of the plantTypes collection.
const plantTypes = useTracker(() => {
return PlantTypesCollection.find();
});
const espIds = useTracker(getAllEspIds);
+ // Variables for the configuration of the selected device.
var selectedEspId;
var selectedAlias = "";
var selectedType;
var selectedMode = null;
+ // Settings from the database for each plant type to be published to the mqtt broker.
const payloadVegi = plantTypes.fetch()[0];
const payloadCacti = plantTypes.fetch()[1];
const payloadFlower = plantTypes.fetch()[2];
+ // If the Name field is empty, log a warning. Else set the entered string as selectedAlias.
const handleChangeAlias = (e) => {
if (e.target.value === "") {
console.log("Nickname cannot be empty!");
@@ -30,6 +34,7 @@ export default function Settings() {
}
}
+ // If no device is selected, log a warning. Else set the selected device as selectedEspId.
const handleChangeDevice = (e) => {
if (e.target.value === "") {
console.log("No device selected!");
@@ -38,6 +43,7 @@ export default function Settings() {
}
}
+ // If no plant type is selected, log a warning. Else set the selected type as selectedType.
const handleChangeType = (e) => {
if (e.target.value === "") {
console.log("No type selected!");
@@ -46,36 +52,45 @@ export default function Settings() {
}
}
+ // If no mode is selected, log a warning. Else set the selected mode as selectedMode.
const handleChangeMode = (e) => {
if (e.target.value === "") {
- console.log("No type selected!");
+ console.log("No mode selected!");
} else {
selectedMode = e.target.value;
}
}
+ // Save the configured device to the database and send the payload data to the mqtt broker.
const handleSendData = (e) => {
var payload = "";
var nmValue = null;
+ var minLux = PlantTypesCollection.findOne({plantType: selectedType}).data.light.minLux;
+
+ // Configure the payload depending on the selected plant type and the selected mode.
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 (selectedMode === "Growth") {nmValue = 450}
if (selectedMode === "Bloom") {nmValue = 700}
-
+ // Log all data
console.log("Payload: " + payload);
console.log("Alias: " + selectedAlias);
console.log("ID: " + selectedEspId);
console.log("Type: " + selectedType);
console.log("Mode: " + selectedMode);
- if ((payload === "") || (nmValue === null) || (selectedEspId === undefined) || (selectedAlias === "") || (selectedType === undefined) || (selectedMode === null)) {alert("Nickname is empty or no device/type/mode selected!");} else {
+ // If one of the values is empty, null or undefined send an alert. Else insert a document in the configuredDevices collection with the entered data,
+ // or update the data if the device has already been configured.
+ if ((payload === "") || (nmValue === null) || (minLux === undefined) || (selectedEspId === undefined) ||
+ (selectedAlias === "") || (selectedType === undefined) || (selectedMode === null)) {alert("Nickname is empty or no device/type/mode selected!");} else {
var doc = ConfiguredDevicesCollection.findOne({deviceId: selectedEspId});
if (doc === undefined) {ConfiguredDevicesCollection.insert({deviceId: selectedEspId, alias: selectedAlias, type: selectedType, mode: selectedMode});} else {
ConfiguredDevicesCollection.update({_id: doc._id}, {$set: {alias: selectedAlias, type: selectedType, mode: selectedMode}});
}
+ // Publish the configuration data of the selected plant type for the selected device to the mqtt broker.
Meteor.call('mqttPublish', {
topic: 'smartgarden/commands/' + selectedEspId + '/soil',
payload: payload
@@ -87,6 +102,7 @@ export default function Settings() {
}
})
+ // Publish the irrigation and light values as true to the mqtt broker, to activate the automatic irrigation and lighting for the selected device.
Meteor.call('mqttPublish', {
topic: 'smartgarden/commands/' + selectedEspId + '/automatic',
payload: JSON.stringify({
@@ -101,10 +117,11 @@ export default function Settings() {
}
})
+ // Publish the minimum lux value of the selected plant type and the nanometer value of the selected mode for the selected device to the mqtt broker.
Meteor.call('mqttPublish', {
topic: 'smartgarden/commands/' + selectedEspId + '/light',
payload: JSON.stringify({
- 'minLX': 500,
+ 'minLX': minLux,
'nm': nmValue
})
}, (err, res) => {
@@ -118,6 +135,7 @@ export default function Settings() {
}
+ //If the data that is loaded from the database has not been fetched yet, show a loading screen. Else load the application.
if ((plantTypes.length <= 0) || (espIds.length <= 0)) {
return (
diff --git a/server/main.js b/server/main.js
index 08fbc82..c47c58d 100644
--- a/server/main.js
+++ b/server/main.js
@@ -8,6 +8,8 @@ var ActiveDeviceCollection = new Meteor.Collection('activeDevice');
var ConfiguredDevicesCollection = new Meteor.Collection('configuredDevices');
Meteor.startup(() => {
+ // If this code is on the meteor server-side, publish all MongoDB collections for the client-side,
+ // start the mqtt-observer script to push data to the database and define the mqttPublish method to be accessible from the client-side.
if(Meteor.isServer) {
Meteor.publish('plantTypesCollection', function() {
return PlantTypesCollection.find();
@@ -25,7 +27,7 @@ Meteor.startup(() => {
return ConfiguredDevicesCollection.find();
})
- startMqttObserver()
+ //startMqttObserver()
Meteor.methods({
'mqttPublish'({ topic, payload }) {
@@ -34,6 +36,7 @@ Meteor.startup(() => {
})
}
+ // If this code is on the meteor client-side, subscribe all published MongoDB collections from the server-side and render the application.
if (Meteor.isClient) {
Meteor.subscribe('plantTypesCollection');
Meteor.subscribe('sensorDataCollection');