Error Handling BackEnd added, logging included, order list and robot class added

This commit is contained in:
caliskanbi 2023-05-15 10:50:19 +02:00
parent 0385aa7792
commit 4e60cd9ad0
12 changed files with 256 additions and 91 deletions

14
.idea/deployment.xml generated Normal file
View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PublishConfigData" serverName="backend" remoteFilesAllowedToDisappearOnAutoupload="false">
<serverData>
<paths name="backend">
<serverdata>
<mappings>
<mapping deploy="/home/lego/SMARTGARDENING" local="$PROJECT_DIR$/software" web="/" />
</mappings>
</serverdata>
</paths>
</serverData>
</component>
</project>

8
.idea/sshConfigs.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SshConfigs">
<configs>
<sshConfig authType="PASSWORD" host="lego-K53SV" id="40359a92-2c87-41df-943b-cfac73c7ea3d" port="22" nameFormat="DESCRIPTIVE" username="lego" useOpenSSHConfig="true" />
</configs>
</component>
</project>

14
.idea/webServers.xml generated Normal file
View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="WebServers">
<option name="servers">
<webServer id="1b3e9e3f-7394-4cbc-a32d-b95ef2abda52" name="backend">
<fileTransfer accessType="SFTP" host="lego-K53SV" port="22" sshConfigId="40359a92-2c87-41df-943b-cfac73c7ea3d" sshConfig="lego@lego-K53SV:22 password">
<advancedOptions>
<advancedOptions dataProtectionLevel="Private" keepAliveTimeout="0" passiveMode="true" shareSSLContext="true" />
</advancedOptions>
</fileTransfer>
</webServer>
</option>
</component>
</project>

View File

@ -29,4 +29,4 @@ urllib3==1.26.14
Werkzeug==2.2.3
zipp==3.15.0
python-ev3dev2==2.1.0.post1
pytest
pytest==7.3.1

Binary file not shown.

View File

@ -10,73 +10,103 @@ from software.defines import Topics, MAX_PLANT_COUNT
import json
import uuid
from typing import Union
from datetime import datetime
import logging
from robot import Robot
# Robot Channel Reactions
def data_sensordata(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase,
order_handler: list):
print("message received")
# TODO: Store data in database
robot: Robot):
str_in = str(message.payload.decode("UTF-8"))
payload = json.loads(str_in)
print("Received data: ", json.dumps(payload))
logging.info("ROBOT_DATA_SENSORDATA Received data: " + json.dumps(payload))
drive_data = {
"PlantID": payload['PlantID'],
"ActionID": payload['ActionID']
}
order_handler.remove(payload['ActionID'])
try:
robot.delete_order(drive_data)
except Exception as e:
logging.error("Could not delete order: " + str(e))
mydatabase.insert_measurement_data(plant_id=payload['PlantID'],
sensordata_temp=payload['AirTemperature'],
sensordata_humidity=payload['AirHumidity'],
sensordata_soil_moisture=payload['SoilMoisture'],
sensordata_brightness=payload['Brightness'])
logging.debug("Inserted to data base: " + json.dumps(payload))
def data_position(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase):
# TODO: Forward to frontend in json format
client.publish(Topics['BACKEND_DATA_POSITION'], message.payload.decode("utf-8"))
def data_position(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, robot: Robot):
logging.info("ROBOT_DATA_POSITION Received data: " + json.dumps(message.payload.decode("UTF-8")))
robot.store_position(json.loads(message.payload.decode("UTF-8"))["Position"])
position_data = {
"Position": robot.get_position(),
"Timestamp": str(datetime.now())
}
client.publish(Topics['BACKEND_DATA_POSITION'], json.dumps(position_data))
def data_battery(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase):
# TODO: Forward to frontend in json format
client.publish(Topics['BACKEND_DATA_BATTERY'], message.payload.decode("utf-8"))
def data_battery(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, robot: Robot):
logging.info("ROBOT_DATA_BATTERY Received data: " + json.dumps(message.payload.decode("UTF-8")))
robot.store_battery(json.loads(message.payload.decode("UTF-8"))["Battery"])
battery_data = {
"Battery": robot.get_battery(),
"Timestamp": str(datetime.now())
}
client.publish(Topics['BACKEND_DATA_BATTERY'], json.dumps(battery_data))
# FrontEnd Channel Reactions
def action_drive(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase,
order_handler: list):
# TODO: ROBOT READY CHECK
if len(order_handler) < 5:
order_handler.append(uuid.uuid4())
robot: Robot):
plant_id = mydatabase.get_plant_id(plant_name=json.loads(message.payload.decode("UTF-8"))["PlantName"])
action_id = str(uuid.uuid4())
drive_data = {
"PlantID": plant_id,
"ActionID": action_id
}
if robot.get_order_number() < 5 and robot.get_robot_status() is True:
robot.add_order(drive_data)
client.publish(Topics['ROBOT_ACTION_DRIVE'], json.dumps(drive_data))
logging.info("BACKEND_ACTION_DRIVE Drive Command published: " + json.dumps(drive_data))
else:
# TODO: What to do when no place in order_list left
pass
client.publish(Topics['ROBOT_ACTION_DRIVE'], message.payload.decode("utf-8"))
if robot.get_order_number() < 5:
robot.add_order(drive_data)
logging.info("BACKEND_ACTION_DRIVE New data added to order list: " + str(drive_data))
elif robot.get_order_number() >= 5:
logging.error("Could not add Order to list. Order discarded")
client.publish(Topics['BACKEND_DATA_ERROR'], "Could not add Order to list. Order discarded")
def action_driveall(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase):
def action_driveall(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase,
robot: Robot):
# TODO: Implement here
pass
print("HELLO")
def action_getposition(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase):
client.publish(Topics['ROBOT_ACTION_GETPOSITION'])
logging.info("BACKEND_ACTION_GETPOSITION message forwarded to Robot")
def action_getbattery(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase):
def action_getbattery(client: mqtt.Client, userdata, message: mqtt.MQTTMessage):
client.publish(Topics['ROBOT_ACTION_GETBATTERY'])
logging.info("BACKEND_ACTION_GETBATTERY message forwarded to Robot")
def action_getalldata(client: mqtt.Client, userdata, message: Union[mqtt.MQTTMessage, list], mydatabase: PlantDataBase):
plant_names = mydatabase.get_plant_names()
print(type(plant_names))
alldata = []
for i in plant_names:
print("I Type: " + str(type(i)))
print("I: " + i[0])
alldata.append(mydatabase.get_latest_data(plant_name=i[0]))
client.publish(Topics['BACKEND_DATA_SENSORDATAALL'], json.dumps(alldata))
print("BACKEND_DATA_SENSORDATAALL SEND DATA:" + str(alldata))
logging.info("BACKEND_DATA_SENSORDATAALL got data from database:" + str(alldata))
def action_newplant(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase):
@ -87,8 +117,7 @@ def action_newplant(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, my
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))
print(mydatabase.get_plant_names())
logging.info("BACKEND_ACTION_NEWPLANT new plant data received and inserted: " + str(plant_data))
action_getalldata(client, userdata, message, mydatabase)
@ -100,14 +129,14 @@ def action_configureplant(client: mqtt.Client, userdata, message: mqtt.MQTTMessa
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))
logging.info("BACKEND_ACTION_CONFIGUREPLANT configure plant data received and inserted: " + str(plant_data))
action_getalldata(client, userdata, message, mydatabase)
def action_deleteplant(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase):
delete_plant = json.loads(message.payload.decode("UTF-8"))
mydatabase.delete_plant(plant_id=delete_plant)
print("BACKEND_ACTION_DELETEPLANT RECEIVED DATA: " + str(delete_plant))
logging.info("BACKEND_ACTION_DELETEPLANT delete plant data received and deleted: " + str(delete_plant))
action_getalldata(client, userdata, message, mydatabase)
@ -118,4 +147,20 @@ def action_countplants(client: mqtt.Client, userdata, message: mqtt.MQTTMessage,
"MaxCount": MAX_PLANT_COUNT
}
client.publish(Topics["BACKEND_DATA_PLANTCOUNT"], json.dumps(count_payload))
print("BACKEND_DATA_PLANTCOUNT SEND DATA: " + str(count_payload))
logging.info("BACKEND_DATA_PLANTCOUNT forwarded plant count to FrontEnd: " + str(count_payload))
def data_error(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, robot: Robot):
robot.store_last_error(message.payload.decode("UTF-8"))
logging.error("ROBOT_DATA_ERROR new error received from Robot: " + robot.get_last_error())
client.publish(Topics['BACKEND_DATA_ERROR'], message.payload.decode("UTF-8"))
def data_robotready(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, robot: Robot):
robot.change_robot_status(message.payload.decode("UTF-8") == 'True')
if robot.get_robot_status() is True and robot.get_order_number() >= 1:
client.publish(Topics['ROBOT_ACTION_DRIVE'], json.dumps(robot.get_next_order()))
logging.info("Waiting Order send to Robot")
logging.info("ROBOT_DATA_ROBOTREADY status updated: " + str(robot.get_robot_status()))
client.publish(Topics['BACKEND_DATA_ROBOTREADY'], message.payload.decode("UTF-8"))

View File

@ -1,10 +1,12 @@
import paho.mqtt.client as mqtt
import software.defines
from software.defines import MQTT_BROKER_LOCAL
from random import randrange, uniform
import time
import json
from software.defines import Topics, PLANTDATA
mqttBroker = "192.168.178.182"
mqttBroker = software.defines.MQTT_BROKER_GLOBAL
def on_connect(client, userdata, flags, rc):
@ -20,17 +22,10 @@ client.on_connect = on_connect
client.connect(mqttBroker)
plantdata = {
"AirTemperature": 20.4,
"AirHumidity": 7.0,
"SoilMoisture": 5.0,
"Brightness": 39,
"PlantID": 2,
"Timestamp": "hallo",
"MeasurementID": 187
"PlantName": "Kemal"
}
print(type(PLANTDATA))
while True:
client.publish("TEST", json.dumps(plantdata))
client.publish("BACKEND/ACTION/GETBATTERY", json.dumps(plantdata))
print(json.dumps(plantdata))
time.sleep(2)

View File

@ -12,17 +12,17 @@ import paho.mqtt.client as mqtt
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(database_name=DATABASE_NAME)
mydatabase.create_tables()
order_handler = [] # will contain UUIDS with Order IDs
import logging
import sys
from robot import Robot
def on_connect(_client: mqtt.Client, _userdata, _flags, _rc):
def on_connect(_client: mqtt.Client, _userdata, _flags, _rc, _mydatabase, _robot):
"""
This method gets called, when it connects to a mqtt broker.
It is used to subscribe to the specific topics
:param _robot:
:param _mydatabase:
:param _client: mqtt client object
:param _userdata:
:param _flags:
@ -37,25 +37,25 @@ def on_connect(_client: mqtt.Client, _userdata, _flags, _rc):
# From Robot:
_client.subscribe(Topics['ROBOT_DATA_SENSORDATA'])
_client.message_callback_add(Topics['ROBOT_DATA_SENSORDATA'], lambda client, userdata, message: data_functions.
data_sensordata(client, userdata, message, mydatabase, order_handler))
data_sensordata(client, userdata, message, _mydatabase, _robot))
_client.subscribe(Topics['ROBOT_DATA_POSITION'])
_client.message_callback_add(Topics['ROBOT_DATA_POSITION'], data_functions.data_position)
_client.subscribe(Topics['ROBOT_DATA_BATTERY'])
_client.message_callback_add(Topics['ROBOT_DATA_BATTERY'], lambda client, userdata, message: data_functions.
data_battery(client, userdata, message, mydatabase))
data_battery(client, userdata, message))
# client.subscribe('Robot/Data/Picture')
# From FrontEnd:
_client.subscribe(Topics['BACKEND_ACTION_DRIVE'])
_client.message_callback_add(Topics['BACKEND_ACTION_DRIVE'], lambda client, userdata, message: data_functions.
action_drive(client, userdata, message, mydatabase, order_handler))
action_drive(client, userdata, message, _mydatabase, _robot))
_client.subscribe(Topics['BACKEND_ACTION_DRIVEALL'])
_client.message_callback_add(Topics['BACKEND_ACTION_DRIVE'], lambda client, userdata, message: data_functions.
action_driveall(client, userdata, message, mydatabase))
_client.message_callback_add(Topics['BACKEND_ACTION_DRIVEALL'], lambda client, userdata, message: data_functions.
action_driveall(client, userdata, message, _mydatabase, _robot))
_client.subscribe(Topics['BACKEND_ACTION_GETPOSITION'])
_client.message_callback_add(Topics['BACKEND_ACTION_GETPOSITION'], data_functions.action_getposition)
@ -66,35 +66,54 @@ def on_connect(_client: mqtt.Client, _userdata, _flags, _rc):
_client.subscribe(Topics['BACKEND_ACTION_GETALLDATA'])
_client.message_callback_add(Topics['BACKEND_ACTION_GETALLDATA'],
lambda client, userdata, message: data_functions.
action_getalldata(client, userdata, message, mydatabase))
action_getalldata(client, userdata, message, _mydatabase))
_client.subscribe(Topics['BACKEND_ACTION_NEWPLANT'])
_client.message_callback_add(Topics['BACKEND_ACTION_NEWPLANT'], lambda client, userdata, message: data_functions.
action_newplant(client, userdata, message, mydatabase))
action_newplant(client, userdata, message, _mydatabase))
_client.subscribe(Topics['BACKEND_ACTION_CONFIGUREPLANT'])
_client.message_callback_add(Topics['BACKEND_ACTION_CONFIGUREPLANT'], lambda client, userdata, message: data_functions.
action_configureplant(client, userdata, message, mydatabase))
action_configureplant(client, userdata, message, _mydatabase))
_client.subscribe(Topics['BACKEND_ACTION_DELETEPLANT'])
_client.message_callback_add(Topics['BACKEND_ACTION_DELETEPLANT'],
lambda client, userdata, message: data_functions.
action_deleteplant(client, userdata, message, mydatabase))
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))
action_countplants(client, userdata, message, _mydatabase))
_client.subscribe(Topics['ROBOT_DATA_ERROR'])
_client.message_callback_add(Topics['ROBOT_DATA_ERROR'], lambda client, userdata, message: data_functions.
data_error(client, userdata, message, _robot))
_client.subscribe(Topics['ROBOT_DATA_ROBOTREADY'])
_client.message_callback_add(Topics['ROBOT_DATA_ROBOTREADY'], lambda client, userdata, message: data_functions.
data_robotready(client, userdata, message, _robot))
# END TOPIC SUBSCRIPTIONS
else:
print("connection failed")
def main():
client = mqtt.Client(BACKEND_CLIENT_ID)
client.on_connect = on_connect
client.connect(MQTT_BROKER_GLOBAL)
client.loop_forever()
robot = Robot()
my_database = PlantDataBase(database_name=DATABASE_NAME)
my_database.create_tables()
mqttclient = mqtt.Client(BACKEND_CLIENT_ID)
mqttclient.on_connect = lambda client, userdata, flags, rc: on_connect(_client=client,
_userdata=userdata,
_flags=flags,
_rc=rc,
_mydatabase=my_database,
_robot=robot)
mqttclient.connect(MQTT_BROKER_GLOBAL)
logging.basicConfig(filename="server.log", filemode="a", encoding="utf-8", level=logging.DEBUG,
format='%(asctime)s %(name)s %(levelname)s %(message)s',
datefmt="%d-%m-%Y %H:%M:%S")
logging.getLogger().addHandler(logging.StreamHandler(sys.stdout))
mqttclient.loop_forever()
if __name__ == "__main__":

View File

@ -1,6 +1,7 @@
# file to create a database via python script
import sqlite3
from typing import Optional
import logging
class PlantDataBase:
@ -13,9 +14,8 @@ class PlantDataBase:
self.conn = None
try:
self.conn = sqlite3.connect(self.db_file, check_same_thread=False)
print(sqlite3.version)
except sqlite3.Error as e:
print(e)
logging.error("Database init error: " + str(e))
self.cur = self.conn.cursor()
def create_tables(self):
@ -38,32 +38,36 @@ class PlantDataBase:
self.cur.execute(table_config)
return True
except sqlite3.Warning as e:
return e
logging.error("Could not create tables: " + str(e))
return False
def insert_plant(self, plantname: str, plant_id: int):
def insert_plant(self, plantname: str, plant_id: int) -> bool:
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
except Exception as e:
logging.error("Could not insert plant: " + str(e))
return False
def configure_plant(self, plant_id: int, plantname: str):
def configure_plant(self, plant_id: int, plantname: str) -> bool:
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
except Exception as e:
logging.error("Could not configure plant: " + str(e))
return False
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
except Exception as e:
logging.error("Could not delete plant: " + str(e))
return False
def insert_measurement_data(self, plant_id,
sensordata_temp,
@ -77,8 +81,9 @@ class PlantDataBase:
f", {sensordata_brightness})")
self.conn.commit()
return True
except (sqlite3.NotSupportedError, sqlite3.Warning) as e:
return e
except Exception as e:
logging.error("Could not insert measurement data: " + str(e))
return False
def get_latest_data(self, plant_name: Optional[str] = None, plant_id: Optional[int] = None):
"""
@ -108,27 +113,42 @@ class PlantDataBase:
"PlantName": plant_name
}
return json_file
except (sqlite3.Warning, TypeError) as e:
return e
except Exception as e:
logging.error("Could not get measurement values: " + str(e))
def delete_data(self, table_name):
try:
self.cur.execute(f'DELETE FROM {table_name}')
self.conn.commit()
return True
# TODO: Kemals Scheiß implementieren
except Exception as e:
logging.error("Could not delete data: " + str(e))
def plant_count(self) -> int:
"""
returns the number of plants registered in the database
:return:
"""
try:
self.cur.execute("SELECT COUNT(*) FROM plants")
return self.cur.fetchone()[0]
except Exception as e:
logging.error("Could not count plants: " + str(e))
def get_plant_names(self) -> list:
try:
self.cur.execute("SELECT PlantName FROM plants")
return self.cur.fetchall()
except Exception as e:
logging.error("Could not get plant names: " + str(e))
def get_plant_id(self, plant_name: str) -> int:
try:
self.cur.execute("SELECT PlantID FROM plants WHERE PlantName=?", (plant_name,))
return self.cur.fetchone()[0]
except Exception as e:
logging.error("Could not get plant id: " + str(e))
def __del__(self):
self.conn.close()

47
software/backend/robot.py Normal file
View File

@ -0,0 +1,47 @@
class Robot:
"""
This class contains the features of the robot. It is used as an interface for the main to avoid global variables and
store them instead in an instance of this robot object
"""
def __init__(self):
self.robot_ready = True
self.order_handler = []
self.battery = 0
self.position = ""
self.last_error = ""
def change_robot_status(self, status: bool):
self.robot_ready = status
def add_order(self, drivedata):
self.order_handler.append(drivedata)
def delete_order(self, drivedata):
self.order_handler.remove(drivedata)
def get_next_order(self):
return self.order_handler[0]
def get_order_number(self):
return len(self.order_handler)
def store_battery(self, battery):
self.battery = battery
def store_position(self, position):
self.position = position
def store_last_error(self, error):
self.last_error = error
def get_battery(self):
return self.battery
def get_position(self):
return self.position
def get_last_error(self):
return self.last_error
def get_robot_status(self):
return self.robot_ready

View File

@ -21,6 +21,8 @@ Topics = {
"ROBOT_DATA_BATTERY": "ROBOT/DATA/BATTERY",
"ROBOT_DATA_POSITION": "ROBOT/DATA/POSITION",
"ROBOT_DATA_PICTURE": "ROBOT/DATA/PICTURE",
"ROBOT_DATA_ERROR": "ROBOT/DATA/ERROR",
"ROBOT_DATA_ROBOTREADY": "ROBOT/DATA/ROBOTREADY",
"BACKEND_ACTION_DRIVE": "BACKEND/ACTION/DRIVE",
"BACKEND_ACTION_DRIVEALL": "BACKEND/ACTION/DRIVEALL",
@ -38,7 +40,8 @@ Topics = {
"BACKEND_DATA_BATTERY": "BACKEND/DATA/BATTERY",
"BACKEND_DATA_PICTURE": "BACKEND/DATA/PICTURE",
"BACKEND_DATA_PLANTCOUNT": "BACKEND/DATA/PLANTCOUNT",
"BACKEND_DATA_ERROR": "BACKEND/DATA/ERROR",
"BACKEND_DATA_ROBOTREADY": "BACKEND/DATA/ROBOTREADY"
}