diff --git a/package-lock.json b/package-lock.json
index 9ba2294..8e27041 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18,6 +18,7 @@
"@rneui/base": "^4.0.0-rc.7",
"@rneui/themed": "^4.0.0-rc.7",
"expo": "~48.0.6",
+ "expo-sqlite": "~11.1.1",
"expo-status-bar": "~1.4.4",
"g": "^2.0.1",
"immer": "^10.0.1",
@@ -31,7 +32,8 @@
"react-native-screens": "~3.20.0",
"react-navigation-stack": "^2.10.4",
"react-redux": "^8.0.5",
- "redux": "^4.2.1"
+ "redux": "^4.2.1",
+ "yup": "^1.2.0"
},
"devDependencies": {
"@babel/core": "^7.20.0"
@@ -2821,6 +2823,18 @@
"resolved": "https://registry.npmjs.org/@expo/vector-icons/-/vector-icons-13.0.0.tgz",
"integrity": "sha512-TI+l71+5aSKnShYclFa14Kum+hQMZ86b95SH6tQUG3qZEmLTarvWpKwqtTwQKqvlJSJrpFiSFu3eCuZokY6zWA=="
},
+ "node_modules/@expo/websql": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@expo/websql/-/websql-1.0.1.tgz",
+ "integrity": "sha512-H9/t1V7XXyKC343FJz/LwaVBfDhs6IqhDtSYWpt8LNSQDVjf5NvVJLc5wp+KCpRidZx8+0+YeHJN45HOXmqjFA==",
+ "dependencies": {
+ "argsarray": "^0.0.1",
+ "immediate": "^3.2.2",
+ "noop-fn": "^1.0.0",
+ "pouchdb-collections": "^1.0.1",
+ "tiny-queue": "^0.2.1"
+ }
+ },
"node_modules/@expo/xcpretty": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@expo/xcpretty/-/xcpretty-4.2.2.tgz",
@@ -5214,6 +5228,11 @@
"sprintf-js": "~1.0.2"
}
},
+ "node_modules/argsarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/argsarray/-/argsarray-0.0.1.tgz",
+ "integrity": "sha512-u96dg2GcAKtpTrBdDoFIM7PjcBA+6rSP0OR94MOReNRyUECL6MtQt5XXmRr4qrftYaef9+l5hcpO5te7sML1Cg=="
+ },
"node_modules/arr-diff": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
@@ -7195,6 +7214,17 @@
"invariant": "^2.2.4"
}
},
+ "node_modules/expo-sqlite": {
+ "version": "11.1.1",
+ "resolved": "https://registry.npmjs.org/expo-sqlite/-/expo-sqlite-11.1.1.tgz",
+ "integrity": "sha512-93KQ4Bc4+xQF2nOZC2jcJ+d0K1ooQSEYganLukw4Sp2LuxUtBsPXYTyOeisSxQSMMaz9nHiTqag8DEC1MiB7Xw==",
+ "dependencies": {
+ "@expo/websql": "^1.0.1"
+ },
+ "peerDependencies": {
+ "expo": "*"
+ }
+ },
"node_modules/expo-status-bar": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-1.4.4.tgz",
@@ -7986,6 +8016,11 @@
"node": ">=4.0"
}
},
+ "node_modules/immediate": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz",
+ "integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q=="
+ },
"node_modules/immer": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/immer/-/immer-10.0.1.tgz",
@@ -10807,6 +10842,11 @@
"url": "https://github.com/sponsors/antelle"
}
},
+ "node_modules/noop-fn": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/noop-fn/-/noop-fn-1.0.0.tgz",
+ "integrity": "sha512-pQ8vODlgXt2e7A3mIbFDlizkr46r75V+BJxVAyat8Jl7YmI513gG5cfyRL0FedKraoZ+VAouI1h4/IWpus5pcQ=="
+ },
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -11424,6 +11464,11 @@
"node": ">=0.10.0"
}
},
+ "node_modules/pouchdb-collections": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/pouchdb-collections/-/pouchdb-collections-1.0.1.tgz",
+ "integrity": "sha512-31db6JRg4+4D5Yzc2nqsRqsA2oOkZS8DpFav3jf/qVNBxusKa2ClkEIZ2bJNpaDbMfWtnuSq59p6Bn+CipPMdg=="
+ },
"node_modules/pretty-bytes": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
@@ -11532,6 +11577,11 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
+ "node_modules/property-expr": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.5.tgz",
+ "integrity": "sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA=="
+ },
"node_modules/pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
@@ -13568,6 +13618,16 @@
"xtend": "~4.0.1"
}
},
+ "node_modules/tiny-case": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz",
+ "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q=="
+ },
+ "node_modules/tiny-queue": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/tiny-queue/-/tiny-queue-0.2.1.tgz",
+ "integrity": "sha512-EijGsv7kzd9I9g0ByCl6h42BWNGUZrlCSejfrb3AKeHC33SGbASu1VDf5O3rRiiUOhAC9CHdZxFPbZu0HmR70A=="
+ },
"node_modules/tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
@@ -13647,6 +13707,11 @@
"node": ">=0.6"
}
},
+ "node_modules/toposort": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
+ "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg=="
+ },
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
@@ -14326,6 +14391,28 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/yup": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/yup/-/yup-1.2.0.tgz",
+ "integrity": "sha512-PPqYKSAXjpRCgLgLKVGPA33v5c/WgEx3wi6NFjIiegz90zSwyMpvTFp/uGcVnnbx6to28pgnzp/q8ih3QRjLMQ==",
+ "dependencies": {
+ "property-expr": "^2.0.5",
+ "tiny-case": "^1.0.3",
+ "toposort": "^2.0.2",
+ "type-fest": "^2.19.0"
+ }
+ },
+ "node_modules/yup/node_modules/type-fest": {
+ "version": "2.19.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
+ "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
+ "engines": {
+ "node": ">=12.20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
}
}
}
diff --git a/package.json b/package.json
index 457334b..9809c80 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
"@rneui/base": "^4.0.0-rc.7",
"@rneui/themed": "^4.0.0-rc.7",
"expo": "~48.0.6",
+ "expo-sqlite": "~11.1.1",
"expo-status-bar": "~1.4.4",
"g": "^2.0.1",
"immer": "^10.0.1",
@@ -32,7 +33,8 @@
"react-native-screens": "~3.20.0",
"react-navigation-stack": "^2.10.4",
"react-redux": "^8.0.5",
- "redux": "^4.2.1"
+ "redux": "^4.2.1",
+ "yup": "^1.2.0"
},
"devDependencies": {
"@babel/core": "^7.20.0"
diff --git a/src/App.js b/src/App.js
index 868546e..887b415 100644
--- a/src/App.js
+++ b/src/App.js
@@ -9,6 +9,7 @@ import HomeScreen from './screens/Home';
import App_2 from './screens/Home/src/components/mqttfile';
import store from './redux/store';
import { Provider } from 'react-redux';
+import PlantsScreen from './screens/PlantsScreen';
const Tab = createBottomTabNavigator();
@@ -19,6 +20,7 @@ const App = () => {
+
diff --git a/src/redux/reducers.js b/src/redux/reducers.js
index f887d49..0ff7125 100644
--- a/src/redux/reducers.js
+++ b/src/redux/reducers.js
@@ -1,13 +1,16 @@
import { ADD_SENSOR_DATA } from './actions';
const initialState = {
- sensorValue: '{"humidity": 0, "temperature": 0, "pressure": 0}',
+ sensorValue: {
+ humidity: 0,
+ temperature: 0,
+ pressure: 0,
+ },
};
const sensorReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_SENSOR_DATA:
- console.log('sensorReducer is sending ' + action.payload);
return {
sensorValue: action.payload,
};
diff --git a/src/screens/Home/index.js b/src/screens/Home/index.js
index 46833f1..51390e5 100644
--- a/src/screens/Home/index.js
+++ b/src/screens/Home/index.js
@@ -1,10 +1,21 @@
import React from 'react';
-import { View, Text, Button, StyleSheet, ScrollView } from 'react-native';
+import {
+ View,
+ Text,
+ Button,
+ StyleSheet,
+ ScrollView,
+ TouchableWithoutFeedback,
+ Keyboard,
+ KeyboardAvoidingView,
+ Platform,
+} from 'react-native';
import Sensor from '../../shared/Sensor';
import ReduxTest from './src/components/ReduxTest';
+import Database from './src/components/Database';
const styles = StyleSheet.create({
container: {
@@ -16,15 +27,19 @@ const styles = StyleSheet.create({
const HomeScreen = () => {
return (
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
);
};
diff --git a/src/screens/Home/src/components/Database.js b/src/screens/Home/src/components/Database.js
new file mode 100644
index 0000000..dcbc3be
--- /dev/null
+++ b/src/screens/Home/src/components/Database.js
@@ -0,0 +1,225 @@
+import React from 'react';
+import {
+ View,
+ Text,
+ TouchableOpacity,
+ ScrollView,
+ TextInput,
+ Button,
+} from 'react-native';
+import { object, string, number, setLocale } from 'yup';
+
+import * as SQLite from 'expo-sqlite';
+const db = SQLite.openDatabase('db.plantDb'); // returns Database object
+
+export default class Database extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ data: null,
+ name: null,
+ lowerLimit: null,
+ upperLimit: null,
+ recLowerLimit: null,
+ recUpperLimit: null,
+ validationError: null,
+ };
+
+ // Check if the plants table exists if not create it
+ db.transaction((tx) => {
+ tx.executeSql(
+ 'CREATE TABLE IF NOT EXISTS plants (id INTEGER PRIMARY KEY AUTOINCREMENT, Name TEXT, LowerLimit INT, UpperLimit INT, RecLowerLimit INT, RecUpperLimit INT)'
+ );
+ });
+
+ this.fetchData();
+ }
+
+ /**
+ * Fetches data from database and stores it in state.
+ */
+ fetchData = () => {
+ db.transaction((tx) => {
+ // sending 4 arguments in executeSql
+ tx.executeSql(
+ 'SELECT * FROM plants',
+ null, // passing sql query and parameters:null
+ // success callback which sends two things Transaction object and ResultSet Object
+ (txObj, { rows: { _array } }) => {
+ this.setState({ ...this.state, data: _array });
+ console.log(_array);
+ },
+ // failure callback which sends two things Transaction object and Error
+ (txObj, error) => console.log('Error ', error)
+ ); // end executeSQL
+ }); // end transaction
+ };
+
+ /**
+ * Validates form input using rules in mySchema. Inserts into plants table if successful. Otherwise, sets this.state.validationError.
+ */
+ handleSubmit = async () => {
+ await mySchema
+ .validate(this.state)
+ .then(() => {
+ this.setState({ ...this.state, validationError: null });
+ db.transaction((tx) => {
+ tx.executeSql(
+ 'INSERT INTO plants (Name, LowerLimit, UpperLimit, RecLowerLimit, RecUpperLimit) VALUES (?, ?, ?, ?, ?)',
+ [
+ this.state.name,
+ this.state.lowerLimit,
+ this.state.upperLimit,
+ this.state.recLowerLimit,
+ this.state.recUpperLimit,
+ ],
+ this.fetchData(),
+ (txObj, err) => console.log('Error ', err)
+ );
+ });
+ })
+ .catch((err) => {
+ this.setState({ ...this.state, validationError: err });
+ console.log(err);
+ });
+ };
+
+ handleClear = () => {
+ db.transaction((tx) => {
+ tx.executeSql('DELETE FROM plants');
+ });
+ this.setState({
+ data: null,
+ name: null,
+ lowerLimit: null,
+ upperLimit: null,
+ recLowerLimit: null,
+ recUpperLimit: null,
+ });
+ };
+
+ render() {
+ return (
+
+ Plant Name
+ this.setState({ name: newData })}
+ />
+ Temperature Lower Limit
+ this.setState({ lowerLimit: newData })}
+ />
+ Temperature Upper Limit
+ this.setState({ upperLimit: newData })}
+ />
+ Temperature Recommended Lower Limit
+ this.setState({ recLowerLimit: newData })}
+ />
+ Temperature Recommended Upper Limit
+ this.setState({ recUpperLimit: newData })}
+ />
+
+
+ {this.state.validationError !== null
+ ? this.state.validationError.message
+ : ''}
+
+
+
+
+
+
+ {JSON.stringify(this.state.data)}
+
+ );
+ }
+}
+
+// Set shared error messages
+setLocale({
+ mixed: {
+ required: 'All fields are required',
+ },
+});
+
+// Validation schema for Yup
+const mySchema = object({
+ name: string().required(),
+ lowerLimit: number().required().typeError('Lower limit must be a number'),
+ upperLimit: number().required().typeError('Upper limit must be a number'),
+ recLowerLimit: number()
+ .required()
+ .when(
+ 'lowerLimit',
+ (lowerLimit, recLowerLimit) =>
+ lowerLimit &&
+ recLowerLimit.min(
+ lowerLimit,
+ 'Recommended lower limit must be greater than lower limit'
+ )
+ )
+ .when(
+ 'recUpperLimit',
+ (recUpperLimit, recLowerLimit) =>
+ recUpperLimit &&
+ recLowerLimit.max(
+ recUpperLimit,
+ 'Recommended lower limit must be less than recommended upper limit'
+ )
+ )
+ .typeError('Recommended lower limit must be a number'),
+ recUpperLimit: number()
+ .required()
+ .when(
+ 'upperLimit',
+ (upperLimit, recUpperLimit) =>
+ upperLimit &&
+ recUpperLimit.max(
+ upperLimit,
+ 'Recommended upper limit must be less than upper limit'
+ )
+ )
+ .typeError('Recommended upper limit must be a number'),
+});
+
+const styles = {
+ container: {
+ flex: 1,
+ marginTop: 20,
+ },
+ textInput: {
+ height: 40,
+ margin: 5,
+ padding: 2,
+ borderWidth: 2,
+ minWidth: 300,
+ },
+ text: {
+ maxWidth: 300,
+ },
+ errorText: {
+ color: 'red',
+ maxWidth: 300,
+ },
+};
diff --git a/src/screens/Home/src/components/ReduxTest.js b/src/screens/Home/src/components/ReduxTest.js
index 3fa8813..86ac485 100644
--- a/src/screens/Home/src/components/ReduxTest.js
+++ b/src/screens/Home/src/components/ReduxTest.js
@@ -9,15 +9,23 @@ export default function ReduxTest() {
const dispatch = useDispatch();
const handlePress = () => {
+ let tempVal = parseInt(Math.random() * 100);
+ let presVal = parseInt(Math.random() * 100);
+ let humVal = parseInt(Math.random() * 100);
+
dispatch({
type: ADD_SENSOR_DATA,
- payload: '{ "temperature": 500, "humidity": 70 }',
+ payload: {
+ temperature: tempVal,
+ pressure: presVal,
+ humidity: humVal,
+ },
});
};
return (
- Hello
+ Press me!
);
}
@@ -27,6 +35,9 @@ const styles = StyleSheet.create({
alignItems: 'center',
width: 50,
height: 50,
- backgroundColor: 'blue',
+ backgroundColor: 'black',
+ },
+ text: {
+ color: 'white',
},
});
diff --git a/src/screens/PlantsScreen.js b/src/screens/PlantsScreen.js
new file mode 100644
index 0000000..ee53716
--- /dev/null
+++ b/src/screens/PlantsScreen.js
@@ -0,0 +1,38 @@
+import React from 'react';
+import {
+ View,
+ Text,
+ Button,
+ StyleSheet,
+ ScrollView,
+ TouchableWithoutFeedback,
+ Keyboard,
+ KeyboardAvoidingView,
+ Platform,
+} from 'react-native';
+import Database from './Home/src/components/Database';
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+});
+
+const PlantsScreen = () => {
+ return (
+
+
+
+
+
+
+
+ );
+};
+
+export default PlantsScreen;
diff --git a/src/shared/Sensor.js b/src/shared/Sensor.js
index 21c082b..fba7aa5 100644
--- a/src/shared/Sensor.js
+++ b/src/shared/Sensor.js
@@ -11,12 +11,18 @@ export default function Sensor({ initial, label, dataType }) {
const value = useSelector((state) => state.sensors.sensorValue); // Get the sensor value from the MQTT sensor in the Redux state
setTimeout(() => {
- for (const [key, val] of Object.entries(JSON.parse(value))) {
+ // Convert JSON string to JSON object
+ let sensorData = value;
+ if (typeof sensorData === 'string') {
+ sensorData = JSON.parse(sensorData);
+ }
+
+ for (const [key, val] of Object.entries(sensorData)) {
if (key === dataType) {
setSensorValue(Number(val));
}
}
- }, 5000);
+ }, 2000);
useEffect(() => {
// Update indicator styling based on sensor value