diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml index 36085a8..b6b05a6 100644 --- a/.idea/dataSources.xml +++ b/.idea/dataSources.xml @@ -8,5 +8,29 @@ jdbc:sqlite:$PROJECT_DIR$/software/backend/backend_database.db $ProjectFileDir$ + + sqlite.xerial + true + org.sqlite.JDBC + jdbc:sqlite:C:\Users\bcali\Documents\Uni\BEI6\Projektarbeit\projektarbeit_duelger_waldhauser_caliskan\software\backend\tests\backend_database.db + $ProjectFileDir$ + + + file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.40.1/sqlite-jdbc-3.40.1.jar + + + + + sqlite.xerial + true + org.sqlite.JDBC + jdbc:sqlite:C:\Users\bcali\Documents\Uni\BEI6\Projektarbeit\projektarbeit_duelger_waldhauser_caliskan\software\backend\tests\test_database.db + $ProjectFileDir$ + + + file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.40.1/sqlite-jdbc-3.40.1.jar + + + \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 24e9b00..c983847 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,3 +29,4 @@ urllib3==1.26.14 Werkzeug==2.2.3 zipp==3.15.0 python-ev3dev2==2.1.0.post1 +pytest \ No newline at end of file diff --git a/software/backend/backend_database.db b/software/backend/backend_database.db index cb236c6..b38b3c6 100644 Binary files a/software/backend/backend_database.db and b/software/backend/backend_database.db differ diff --git a/software/backend/createdatabase_and_test.py b/software/backend/createdatabase.py similarity index 58% rename from software/backend/createdatabase_and_test.py rename to software/backend/createdatabase.py index 454c505..989ee18 100644 --- a/software/backend/createdatabase_and_test.py +++ b/software/backend/createdatabase.py @@ -2,22 +2,20 @@ import random from plantdatabase import PlantDataBase -mydatabase = PlantDataBase() -mydatabase.create_table() +mydatabase = PlantDataBase('backend_database.db') +mydatabase.create_tables() -for i in range(1,6): - mydatabase.insert_plant(_gps='gps', plantname=f"Pflanze{i}") +for i in range(1, 6): + mydatabase.insert_plant(plantname=f"Pflanze{i}") -for i in range(1,7): +for i in range(1, 7): plant_id = i temp = random.random() humidity = random.random() soil_moisture = random.random() - pest_infestation = 0 light_intensity = random.random() mydatabase.insert_measurement_data(plant_id=plant_id, sensordata_temp=temp, sensordata_humidity=humidity, sensordata_soil_moisture=soil_moisture, - pest_infestation=pest_infestation, - light_intensity=light_intensity) + sensordata_brightness=light_intensity) diff --git a/software/backend/data_functions.py b/software/backend/data_functions.py index 8ecd4fa..029f9e7 100644 --- a/software/backend/data_functions.py +++ b/software/backend/data_functions.py @@ -6,7 +6,7 @@ Every function should return json format with the wanted data from the database """ import paho.mqtt.client as mqtt from plantdatabase import PlantDataBase -from software.defines import Topics +from software.defines import Topics, MAX_PLANT_COUNT import json import uuid @@ -27,8 +27,7 @@ def data_sensordata(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, my sensordata_temp=payload['AirTemperature'], sensordata_humidity=payload['AirHumidity'], sensordata_soil_moisture=payload['SoilMoisture'], - pest_infestation=0, - light_intensity=payload['Brightness']) + sensordata_brightness=payload['Brightness']) def data_position(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase): @@ -68,23 +67,46 @@ def action_getbattery(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, def action_getalldata(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase): - # TODO: get data from database + plant_names = json.loads(message.payload.decode("UTF-8")) + print(plant_names) alldata = [] - for i in range(1, 7): - alldata.append(mydatabase.get_latest_data(plant_id=i)) + for i in plant_names: + alldata.append(mydatabase.get_latest_data(plant_name=i)) client.publish(Topics['BACKEND_DATA_SENSORDATAALL'], json.dumps(alldata)) def action_newplant(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase): - # TODO: insert new plant to database - pass + plant_data = json.loads(message.payload.decode("UTF-8")) + mydatabase.insert_plant(plantname=plant_data["PlantName"], plant_id=plant_data["PlantID"]) + mydatabase.insert_measurement_data(plant_id=plant_data["PlantID"], + sensordata_temp=plant_data["AirTemperature"], + sensordata_humidity=plant_data["AirHumidity"], + sensordata_soil_moisture=plant_data["SoilMoisture"], + sensordata_brightness=plant_data["Brightness"]) + print("BACKEND_ACTION_NEWPLANT RECEIVED DATA: " + str(plant_data)) def action_configureplant(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase): - # TODO: configure plant - pass + plant_data = json.loads(message.payload.decode("UTF-8")) + mydatabase.configure_plant(plant_id=plant_data["plant_ID"], plantname=plant_data["PlantName"]) + mydatabase.insert_measurement_data(plant_id=plant_data["PlantID"], + sensordata_temp=plant_data["AirTemperature"], + sensordata_humidity=plant_data["AirHumidity"], + sensordata_soil_moisture=plant_data["SoilMoisture"], + sensordata_brightness=plant_data["Brightness"]) + print("BACKEND_ACTION_CONFIGUREPLANT RECEIVED DATA: " + str(plant_data)) def action_deleteplant(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase): - # TODO: delete plant from database (from ID) - pass + delete_plant = json.loads(message.payload.decode("UTF-8")) + mydatabase.delete_plant(plant_id=delete_plant["PlantID"]) + print("BACKEND_ACTION_DELETEPLANT RECEIVED DATA: " + str(delete_plant)) + + +def action_countplants(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase): + count_payload = { + "CurrentCount": mydatabase.plant_count(), + "MaxCount": MAX_PLANT_COUNT + } + print("BACKEND_DATA_PLANTCOUNT SENDED DATA:" + str(count_payload)) + client.publish(Topics["BACKEND_ACTION_PLANTCOUNT"], json.dumps(count_payload, indent=4)) diff --git a/software/backend/main.py b/software/backend/main.py index c0bc31e..8aae264 100644 --- a/software/backend/main.py +++ b/software/backend/main.py @@ -9,13 +9,13 @@ Used protocol for interaction: mqtt (paho-mqtt module) # imports import paho.mqtt.client as mqtt -from software.defines import MQTT_BROKER_LOCAL, MQTT_BROKER_GLOBAL, Topics, BACKEND_CLIENT_ID +from software.defines import MQTT_BROKER_LOCAL, MQTT_BROKER_GLOBAL, Topics, BACKEND_CLIENT_ID, DATABASE_NAME from plantdatabase import PlantDataBase import data_functions # inits -mydatabase = PlantDataBase() -mydatabase.create_table() +mydatabase = PlantDataBase(database_name=DATABASE_NAME) +mydatabase.create_tables() order_handler = [] # will contain UUIDS with Order IDs @@ -81,6 +81,10 @@ def on_connect(_client: mqtt.Client, _userdata, _flags, _rc): lambda client, userdata, message: data_functions. action_deleteplant(client, userdata, message, mydatabase)) + _client.subscribe(Topics['BACKEND_ACTION_PLANTCOUNT']) + _client.message_callback_add(Topics['BACKEND_ACTION_PLANTCOUNT'], lambda client, userdata, message: data_functions. + action_countplants(client, userdata, message, mydatabase)) + # END TOPIC SUBSCRIPTIONS else: print("connection failed") diff --git a/software/backend/plantdatabase.py b/software/backend/plantdatabase.py index 531ea58..4a30a1b 100644 --- a/software/backend/plantdatabase.py +++ b/software/backend/plantdatabase.py @@ -1,13 +1,15 @@ # file to create a database via python script import sqlite3 +from typing import Optional class PlantDataBase: """ Class to create Makeathon database """ - def __init__(self): - self.db_file = 'backend_database.db' + + def __init__(self, database_name: str): + self.db_file = database_name # 'backend_database.db' self.conn = None try: self.conn = sqlite3.connect(self.db_file, check_same_thread=False) @@ -16,69 +18,113 @@ class PlantDataBase: print(e) self.cur = self.conn.cursor() - def create_table(self): - table_config = "CREATE TABLE IF NOT EXISTS plants " \ - "(plant_ID INTEGER PRIMARY KEY AUTOINCREMENT," \ - "plantName TEXT)" - self.cur.execute(table_config) + def create_tables(self): + try: + table_config = "CREATE TABLE IF NOT EXISTS plants " \ + "(PlantID INTEGER PRIMARY KEY," \ + "PlantName TEXT)" + self.cur.execute(table_config) - table_config = "CREATE TABLE IF NOT EXISTS measurement_values " \ - "(measurement_id INTEGER PRIMARY KEY AUTOINCREMENT," \ - "Timestamp DATETIME DEFAULT CURRENT_TIMESTAMP," \ - "plant_ID INTEGER, " \ - "sensordata_temp REAL," \ - "sensordata_humidity REAL," \ - "sensordata_soil_moisture REAL," \ - "light_intensity REAL," \ - "FOREIGN KEY (plant_ID)" \ - "REFERENCES plants (plant_ID) )" - self.cur.execute(table_config) + table_config = "CREATE TABLE IF NOT EXISTS measurement_values " \ + "(measurementID INTEGER PRIMARY KEY," \ + "Timestamp DATETIME DEFAULT CURRENT_TIMESTAMP," \ + "PlantID INTEGER, " \ + "AirTemperature REAL," \ + "AirHumidity REAL," \ + "SoilMoisture REAL," \ + "Brightness REAL," \ + "FOREIGN KEY (PlantID)" \ + "REFERENCES plants (PlantID) )" + self.cur.execute(table_config) + return True + except sqlite3.Warning as e: + return e - def insert_plant(self, _gps: str, plantname): - self.cur.execute("INSERT INTO plants (gps, plantName) VALUES (?,?)", (_gps, plantname)) - self.conn.commit() + def insert_plant(self, plantname: str, plant_id: int): + try: + self.cur.execute("INSERT INTO plants (PlantName, PlantID) VALUES (?,?)", (plantname, plant_id)) + self.conn.commit() + return True + except (sqlite3.NotSupportedError, sqlite3.Warning) as e: + return e + + def configure_plant(self, plant_id: int, plantname: str): + try: + self.cur.execute("UPDATE plants SET PlantID = ?, PlantName = ? WHERE PlantID= ?", + (plant_id, plantname, plant_id)) + self.conn.commit() + return True + except (sqlite3.NotSupportedError, sqlite3.Warning) as e: + return e + + def delete_plant(self, plant_id): + try: + self.cur.execute('DELETE FROM plants WHERE PlantID = ?', (plant_id,)) + self.conn.commit() + return True + except (sqlite3.NotSupportedError, sqlite3.Warning) as e: + return e def insert_measurement_data(self, plant_id, sensordata_temp, sensordata_humidity, sensordata_soil_moisture, - light_intensity): - self.cur.execute(f"INSERT INTO measurement_values (plant_ID, sensordata_temp, sensordata_humidity," - f" sensordata_soil_moisture, light_intensity) VALUES " - f"({plant_id}, {sensordata_temp}, {sensordata_humidity}, {sensordata_soil_moisture}" - f", {light_intensity})") - self.conn.commit() + sensordata_brightness) -> bool: + try: + self.cur.execute(f"INSERT INTO measurement_values (PlantID, AirTemperature, AirHumidity," + f"SoilMoisture, Brightness) VALUES " + f"({plant_id}, {sensordata_temp}, {sensordata_humidity}, {sensordata_soil_moisture}" + f", {sensordata_brightness})") + self.conn.commit() + return True + except (sqlite3.NotSupportedError, sqlite3.Warning) as e: + return e - def get_latest_data(self, plant_id) -> dict: + def get_latest_data(self, plant_name: Optional[str] = None, plant_id: Optional[int] = None): """ Gets the newest parameter of specific plant and returns all parameters in json format :param plant_id: + :param plant_name: :return: """ - self.cur.execute(f"SELECT * FROM measurement_values where plant_ID = {plant_id} ORDER BY Timestamp DESC LIMIT 1") - data = self.cur.fetchone() - self.cur.execute(f"SELECT plantName FROM plants where plant_ID = {plant_id}") - name = self.cur.fetchone() - print(data) - print(name[0]) - json_file = { - "MeasurementID": data[0], - "PlantID": data[2], - "Timestamp": data[1], - "AirTemperature": data[3], - "AirHumidity": data[4], - "SoilMoisture": data[5], - "Brightness": data[6], - "PlantName": name - } - return json_file + try: + if plant_name is not None and plant_id is None: + self.cur.execute("SELECT PlantID FROM plants where PlantName = ?", (plant_name,)) + plant_id = self.cur.fetchone()[0] + elif (plant_id is not None and plant_name is not None) or (plant_id is None and plant_name is None): + raise TypeError("Can't pass plant_id and plant_name to the function. Just one allowed !") + + self.cur.execute("SELECT * FROM measurement_values where PlantID = ? ORDER BY Timestamp DESC LIMIT 1", + (plant_id,)) + data = self.cur.fetchone() + json_file = { + "MeasurementID": data[0], + "PlantID": data[2], + "Timestamp": data[1], + "AirTemperature": data[3], + "AirHumidity": data[4], + "SoilMoisture": data[5], + "Brightness": data[6], + "PlantName": plant_name + } + return json_file + except (sqlite3.Warning, TypeError) as e: + return e def delete_data(self, table_name): - self.cur.execute(f"DELETE FROM {table_name}") + self.cur.execute(f'DELETE FROM {table_name}') self.conn.commit() + return True # TODO: Kemals Scheiß implementieren - def delete_plant(self, plant_id): - self.cur.execute('DELETE FROM plants WHERE plant_ID = ?', (plant_id,)) - self.conn.commit() + def plant_count(self) -> int: + """ + returns the number of plants registered in the database + :return: + """ + self.cur.execute("SELECT COUNT(*) FROM plants") + return self.cur.fetchone()[0] + + def __del__(self): + self.conn.close() diff --git a/software/backend/tests/test_database.db b/software/backend/tests/test_database.db new file mode 100644 index 0000000..d38a441 Binary files /dev/null and b/software/backend/tests/test_database.db differ diff --git a/software/backend/tests/test_plantdatabase.py b/software/backend/tests/test_plantdatabase.py new file mode 100644 index 0000000..73b9578 --- /dev/null +++ b/software/backend/tests/test_plantdatabase.py @@ -0,0 +1,50 @@ +# +# created by caliskan +# use this file to test your plantdatabase changes + +from software.backend.plantdatabase import PlantDataBase +import pytest + + +def test_create_table(): + testdatabase = PlantDataBase(database_name='test_database.db') + assert testdatabase.create_tables() is True + + +def test_insert_and_delete_plant(): + testdatabase = PlantDataBase(database_name='test_database.db') + + assert testdatabase.create_tables() is True + assert testdatabase.delete_data("plants") is True + assert testdatabase.insert_plant(plantname="Bertha", plant_id=1) is True + assert testdatabase.plant_count() == 1 + assert testdatabase.delete_plant(plant_id=1) is True + assert testdatabase.plant_count() == 0 + + +def test_insert_and_get_measurement_values(): + test_plant_id = 2 + test_temp = 22.4 + test_humidity = 93.4 + test_soil_moisture = 12.5 + test_brightness = 66 + test_plant_name = "Bertha" + + testdatabase = PlantDataBase(database_name='test_database.db') + assert testdatabase.create_tables() is True + assert testdatabase.delete_data("plants") is True + assert testdatabase.insert_plant(plantname=test_plant_name, plant_id=test_plant_id) is True + + assert testdatabase.insert_measurement_data(plant_id=test_plant_id, + sensordata_temp=test_temp, + sensordata_humidity=test_humidity, + sensordata_soil_moisture=test_soil_moisture, + sensordata_brightness=test_brightness) is True + test_plant_data = testdatabase.get_latest_data(plant_name=test_plant_name) + print(test_plant_data) + assert test_plant_data["PlantID"] == test_plant_id + assert test_plant_data["AirTemperature"] == test_temp + assert test_plant_data["AirHumidity"] == test_humidity + assert test_plant_data["SoilMoisture"] == test_soil_moisture + assert test_plant_data["Brightness"] == test_brightness + assert test_plant_data["PlantName"] == test_plant_name diff --git a/software/defines.py b/software/defines.py index 974eca1..9bc3e77 100644 --- a/software/defines.py +++ b/software/defines.py @@ -8,6 +8,8 @@ MQTT_BROKER_LOCAL = "192.168.0.199" MQTT_BROKER_GLOBAL = "mqtt.eclipseprojects.io" RASPI_CLIENT_ID = "smart_farming_raspi" BACKEND_CLIENT_ID = "smart_farming_server" +MAX_PLANT_COUNT = 6 +DATABASE_NAME = 'backend_database.db' # Topics: Topics = { @@ -21,7 +23,7 @@ Topics = { "ROBOT_DATA_PICTURE": "ROBOT/DATA/PICTURE", "BACKEND_ACTION_DRIVE": "BACKEND/ACTION/DRIVE", - "BACKEND_ACTION_DRIVEPALL": "BACKEND/ACTION/DRIVEALL", + "BACKEND_ACTION_DRIVEALL": "BACKEND/ACTION/DRIVEALL", "BACKEND_ACTION_GETPOSITION": "BACKEND/ACTION/GETPOSITION", "BACKEND_ACTION_GETBATTERY": "BACKEND/ACTION/GETBATTERY", "BACKEND_ACTION_GETALLDATA": "BACKEND/ACTION/GETALLDATA", @@ -111,7 +113,7 @@ BATTERY = { PLANTCOUNT = { "CurrenCount": 0, - "maxCount": 0 + "MaxCount": 0 } # endregion @@ -136,6 +138,8 @@ DELETEPLANT = { # GETBATTERY -> no message needed -# GETALLDATA -> no message needed +GETALLDATA = { + "PlantNames": [] +} # endregion