Compare commits
10
Commits
e975585f57
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
556736ccf5 | ||
|
|
410e27adc3 | ||
|
|
f6f6421620 | ||
|
|
5e14633f42 | ||
|
|
1778a211bb | ||
|
|
22bcf7310f | ||
|
|
90235a73f1 | ||
|
|
2c065573a5 | ||
|
|
2a3136503a | ||
|
|
d9ad3bce0c |
No files matched your search
Generated
+1
-1
@@ -3,7 +3,7 @@
|
||||
<component name="WebServers">
|
||||
<option name="servers">
|
||||
<webServer id="1b3e9e3f-7394-4cbc-a32d-b95ef2abda52" name="backend">
|
||||
<fileTransfer accessType="SFTP" host="192.168.137.197" port="22" sshConfigId="8ca8aafc-5b14-4074-b370-599a5b56368a" sshConfig="lego@192.168.137.197:22 password">
|
||||
<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>
|
||||
|
||||
Binary file not shown.
+4442
-2946
File diff suppressed because it is too large.
Load diff
@@ -24,8 +24,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-IMG">
|
||||
<img class="IMG-Plants" src="assets/images/gruppe1.jpg" alt="Plant is not available">
|
||||
<img class="IMG-Plants" src="assets/images/gruppe2.jpg" alt="Plant is not available">
|
||||
<img class="IMG-Plants" src="assets/images/Pflanze.png" alt="Plant is not available">
|
||||
<img class="IMG-Plants" src="assets/images/Pflanze.png" alt="Plant is not available">
|
||||
</div>
|
||||
<div class="item-delete">
|
||||
<button class="item-Configure" mat-icon-button color="primary" (click)="onConfigurePlant()">
|
||||
|
||||
@@ -19,7 +19,6 @@ export class PlantsComponent {
|
||||
filteredOptions: Observable<string[]>;
|
||||
plants$: Observable<Plant[]> = this.storeService.currentPlants;
|
||||
|
||||
|
||||
constructor(public dialog: MatDialog, public storeService: StoreService, private mqttRequestService: MqttRequestService) {
|
||||
// sobald es eine änderung bei den pflanzenNamen gibt, wird bei der suchleiste ein neuer filter eingesetzt
|
||||
this.options$.subscribe(response => {
|
||||
@@ -58,12 +57,10 @@ export class PlantsComponent {
|
||||
this.selectedPlant = value;
|
||||
}
|
||||
|
||||
|
||||
onOptionDefault() {
|
||||
this.selectedPlant = 'undefined';
|
||||
}
|
||||
|
||||
|
||||
private _filter(value: string, options: string[]): string[] {
|
||||
const filterValue = value.toLowerCase();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="container"
|
||||
*ngIf="robotBatteryResponse$ !== undefined && (robotBatteryResponse$ |async) !== undefined; else errorLoadingData">
|
||||
<div class="robotImage">
|
||||
<img src="assets/images/gruppe1.jpg" alt="Plant is not available">
|
||||
<img src="assets/images/Robot.png" alt="Plant is not available">
|
||||
</div>
|
||||
<div class="robotAkku">
|
||||
<h1 class="akkuMsg">Robot Akku:</h1>
|
||||
|
||||
@@ -66,19 +66,6 @@ export class MqttRequestService {
|
||||
console.error('Error:', err);
|
||||
});
|
||||
|
||||
// this.mqttService.subscribeToTopic("BACKEND/DATA/POSITION").subscribe(data => {
|
||||
// if (typeof data !== "object") {
|
||||
// const payload = JSON.parse(data) as Position;
|
||||
// this.mqttSetDataService.setRobotPosition(payload);
|
||||
// }
|
||||
// }, err => {
|
||||
// this.dialog.open(ErrorDialogComponent, {
|
||||
// data: err,
|
||||
// width: '400px',
|
||||
// });
|
||||
// console.error('Error:', err);
|
||||
// });
|
||||
|
||||
this.mqttService.subscribeToTopic("BACKEND/DATA/BATTERY").subscribe(data => {
|
||||
if (typeof data !== "object") {
|
||||
const payload = JSON.parse(data) as RobotBattery;
|
||||
@@ -119,22 +106,6 @@ export class MqttRequestService {
|
||||
console.error('Error:', err);
|
||||
});
|
||||
|
||||
// this.mqttService.subscribeToTopic("BACKEND/DATA/PICTURE").subscribe(data => {
|
||||
// if (typeof data !== "object") {
|
||||
// const payload = JSON.parse(data) as Plant;
|
||||
// this.mqttSetDataService.setDataPlant(payload);
|
||||
// }
|
||||
// }, err => {
|
||||
// console.error('Error:', err);
|
||||
// });
|
||||
|
||||
//Request to get ALL DATA every 10sec!!
|
||||
// this.timerSubscription = timer(0, 10000).pipe(
|
||||
// map(() => {
|
||||
// this.mqttService.publishToTopic('BACKEND/ACTION/GETALLDATA');
|
||||
// })
|
||||
// ).subscribe();
|
||||
|
||||
//Publish to the Topic to recieve the first Data
|
||||
this.publishToPLANTCOUNT();
|
||||
this.publishToGETBATTERY();
|
||||
@@ -145,18 +116,14 @@ export class MqttRequestService {
|
||||
publishToGETALLDATA() {
|
||||
this.mqttService.publishToTopic('BACKEND/ACTION/GETALLDATA', JSON.stringify(this.storeService.getAllPlantNames()));
|
||||
}
|
||||
publishToDRIVE() {
|
||||
this.mqttService.publishToTopic('BACKEND/ACTION/DRIVE');
|
||||
}
|
||||
|
||||
publishToGETBATTERY() {
|
||||
this.mqttService.publishToTopic('BACKEND/ACTION/GETBATTERY');
|
||||
}
|
||||
|
||||
publishToROBOTREADY() {
|
||||
this.mqttService.publishToTopic('BACKEND/ACTION/ROBOTREADY');
|
||||
}
|
||||
publishToGETPOSITION() {
|
||||
this.mqttService.publishToTopic('BACKEND/ACTION/GETPOSITION');
|
||||
}
|
||||
|
||||
publishToNEWPLANT(plant: Plant) {
|
||||
this.mqttService.publishToTopic('BACKEND/ACTION/NEWPLANT', JSON.stringify(plant));
|
||||
@@ -173,6 +140,7 @@ export class MqttRequestService {
|
||||
publishToPLANTCOUNT() {
|
||||
this.mqttService.publishToTopic('BACKEND/ACTION/PLANTCOUNT');
|
||||
}
|
||||
|
||||
/**
|
||||
* publishToINPUT wird die Spracheingabe übermittelt, die dann mit
|
||||
* mehreren if else anweisungen einen Request an das Backend sendet und
|
||||
|
||||
@@ -32,5 +32,4 @@ export class MqttSetDataService {
|
||||
setPlantCount(data: PlantCount): void {
|
||||
this.storeService.setPlantCount(data);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,8 +10,8 @@ export class MqttService {
|
||||
|
||||
// Connection zu dem MQTT Broker
|
||||
constructor() {
|
||||
// this.client = connect('wss://mqtt.eclipseprojects.io:443/mqtt'); //Je nachdem welchen Link der Broker hat
|
||||
this.client = connect('mqtt://192.168.137.197:1883', { clientId: 'kemal' });
|
||||
this.client = connect('wss://mqtt.eclipseprojects.io:443/mqtt'); //Online Broker
|
||||
// this.client = connect('mqtt://192.168.137.197:1883', { clientId: 'kemal' }); //lokaler Broker
|
||||
}
|
||||
|
||||
public subscribeToTopic(topic: string): Observable<any> {
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
<img class="img-item" src="assets/images/logo.png" alt="not available">
|
||||
</header>
|
||||
|
||||
|
||||
<body>
|
||||
<router-outlet></router-outlet>
|
||||
</body>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="container">
|
||||
<p class="item-header" mat-dialog-title>Map</p>
|
||||
<img class="item-img" src="assets/images/gruppe1.jpg" alt="not available">
|
||||
<img class="item-img" src="assets/images/Map.png" alt="not available">
|
||||
<div class="item-button" mat-dialog-actions>
|
||||
<button mat-button mat-dialog-close>Close</button>
|
||||
</div>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 8.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 313 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 218 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 114 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 257 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 257 KiB |
Binary file not shown.
@@ -3,6 +3,7 @@ created by caliskan at 19.04.2023
|
||||
|
||||
This file contains all functions, which handle the different cases.
|
||||
Every function should return json format with the wanted data from the database
|
||||
The functions are called, when data is received on the according channel
|
||||
"""
|
||||
import paho.mqtt.client as mqtt
|
||||
from plantdatabase import PlantDataBase
|
||||
@@ -19,105 +20,249 @@ from robot import Robot
|
||||
|
||||
def data_sensordata(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase,
|
||||
robot: Robot):
|
||||
"""
|
||||
This function is used to store received data from the robot inside the plant_database
|
||||
USAGE FOR DATA OF ONE PLANT
|
||||
:param client: mqtt client
|
||||
:param userdata:
|
||||
:param message: received data
|
||||
:param mydatabase: database information
|
||||
:param robot: robot object
|
||||
:return: none
|
||||
"""
|
||||
|
||||
# Load the message and convert to json
|
||||
str_in = str(message.payload.decode("UTF-8"))
|
||||
payload = json.loads(str_in)
|
||||
|
||||
logging.info("ROBOT_DATA_SENSORDATA Received data: " + json.dumps(payload))
|
||||
drive_data = {
|
||||
"PlantID": payload['PlantID'],
|
||||
"PlantID": [payload['PlantID']],
|
||||
"ActionID": payload['ActionID']
|
||||
}
|
||||
|
||||
try:
|
||||
# Delete order from order list
|
||||
robot.delete_order(drive_data)
|
||||
# Save data in database
|
||||
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))
|
||||
|
||||
# Send received data to frontend
|
||||
action_getalldata(client, userdata, message, mydatabase)
|
||||
except Exception as e:
|
||||
logging.error("Could not delete order: " + str(e))
|
||||
|
||||
|
||||
def data_sensordataall(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase,
|
||||
robot: Robot):
|
||||
"""
|
||||
This function is used to store received data from the robot inside the plant_database
|
||||
USAGE FOR DATA OF ALL PLANTS
|
||||
:param client:
|
||||
:param userdata:
|
||||
:param message:
|
||||
:param mydatabase:
|
||||
:param robot:
|
||||
:return: none
|
||||
"""
|
||||
|
||||
# Load the message and convert to json
|
||||
str_in = str(message.payload.decode("UTF-8"))
|
||||
payload = json.loads(str_in)
|
||||
logging.info("ROBOT_DATA_SENSORDATAALL Received data: " + json.dumps(payload))
|
||||
|
||||
# Create list of plant_ids and create dataset
|
||||
plant_ids = []
|
||||
for i in payload['SensorData']:
|
||||
plant_ids.append(i["PlantID"])
|
||||
drive_data = {
|
||||
"PlantID": plant_ids,
|
||||
"ActionID": payload['ActionID']
|
||||
}
|
||||
|
||||
try:
|
||||
# Delete order from order list
|
||||
robot.delete_order(drive_data)
|
||||
|
||||
# Insert all received data files in plant_database
|
||||
for i in payload['SensorData']:
|
||||
mydatabase.insert_measurement_data(plant_id=i['PlantID'],
|
||||
sensordata_temp=i['AirTemperature'],
|
||||
sensordata_humidity=i['AirHumidity'],
|
||||
sensordata_soil_moisture=i['SoilMoisture'],
|
||||
sensordata_brightness=i['Brightness'])
|
||||
logging.debug("Inserted to data base: " + json.dumps(payload))
|
||||
|
||||
# Send all the plant data to the frontend
|
||||
action_getalldata(client, userdata, message, mydatabase)
|
||||
except Exception as e:
|
||||
logging.error("Could not delete order: " + str(e))
|
||||
|
||||
|
||||
def data_position(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, robot: Robot):
|
||||
"""
|
||||
This function is used to receive the robot position and insert it in the robot object
|
||||
:param client: mqtt client
|
||||
:param userdata:
|
||||
:param message: received data
|
||||
:param robot: robot object to store position
|
||||
:return: none
|
||||
"""
|
||||
|
||||
logging.info("ROBOT_DATA_POSITION Received data: " + json.dumps(message.payload.decode("UTF-8")))
|
||||
|
||||
# Store received position data in robot object
|
||||
robot.store_position(json.loads(message.payload.decode("UTF-8"))["Position"])
|
||||
position_data = {
|
||||
"Position": robot.get_position(),
|
||||
"Timestamp": str(datetime.now())
|
||||
}
|
||||
# Send the position as a json object to the frontend channel
|
||||
client.publish(Topics['BACKEND_DATA_POSITION'], json.dumps(position_data))
|
||||
|
||||
|
||||
def data_battery(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, robot: Robot):
|
||||
"""
|
||||
This function is used to receive the robot position and insert it in the robot object
|
||||
:param client: mqtt client object
|
||||
:param userdata:
|
||||
:param message: received data
|
||||
:param robot: robot object to store the received information
|
||||
:return: none
|
||||
"""
|
||||
|
||||
logging.info("ROBOT_DATA_BATTERY Received data: " + str(json.dumps(message.payload.decode("UTF-8"))))
|
||||
# Store battery status in robot object
|
||||
robot.store_battery(json.loads(message.payload.decode("UTF-8"))["Battery"])
|
||||
battery_data = {
|
||||
"Battery": robot.get_battery(),
|
||||
"Timestamp": str(datetime.now())
|
||||
}
|
||||
# Send Battery status and Robot-Ready Status as json objects to frontend
|
||||
client.publish(Topics['BACKEND_DATA_BATTERY'], json.dumps(battery_data))
|
||||
client.publish(Topics['BACKEND_DATA_ROBOTREADY'], str(robot.get_robot_status()))
|
||||
|
||||
|
||||
def data_error(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, robot: Robot):
|
||||
"""
|
||||
This function is called when the robot sends an error message and forwards it to the frontend
|
||||
:param client: mqtt client
|
||||
:param userdata:
|
||||
:param message: received error message
|
||||
:param robot: robot objectg
|
||||
:return: none
|
||||
"""
|
||||
|
||||
# Store last error in robot object
|
||||
robot.store_last_error(message.payload.decode("UTF-8"))
|
||||
# Write error into server log
|
||||
logging.error("ROBOT_DATA_ERROR new error received from Robot: " + robot.get_last_error())
|
||||
# Send error data to FrontEnd Channel to display it to the user
|
||||
client.publish(Topics['BACKEND_DATA_ERROR'], message.payload.decode("UTF-8"))
|
||||
|
||||
|
||||
def data_robotready(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, robot: Robot):
|
||||
"""
|
||||
This function is used to update the Robot-Ready Status of the Robot and inform the FrontEnd about it
|
||||
:param client: mqtt client
|
||||
:param userdata:
|
||||
:param message: received data
|
||||
:param robot: robot object
|
||||
:return: none
|
||||
"""
|
||||
|
||||
# Update the robot status
|
||||
robot.change_robot_status(message.payload.decode("UTF-8") == 'True')
|
||||
|
||||
# If possible send new waiting order to the robot
|
||||
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"))
|
||||
|
||||
# Send new robot-ready status to frontend channel
|
||||
client.publish(Topics['BACKEND_DATA_ROBOTREADY'], str(robot.get_robot_status()))
|
||||
|
||||
|
||||
# FrontEnd Channel Reactions
|
||||
|
||||
def action_drive(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase,
|
||||
robot: Robot):
|
||||
plant_id = mydatabase.get_plant_id(plant_name=json.loads(message.payload.decode("UTF-8"))["PlantName"])
|
||||
"""
|
||||
This function is called when a drive command from the FrontEnd is received and forwards the order to the robot or
|
||||
stores it in the order list.
|
||||
:param client: mqtt client object
|
||||
:param userdata:
|
||||
:param message: information of plant to drive to
|
||||
:param mydatabase: plant_database
|
||||
:param robot: robot object
|
||||
:return: none
|
||||
"""
|
||||
# Get PlantID from received PlantName
|
||||
plant_id = mydatabase.get_plant_id(plant_name=json.loads(str(message.payload.decode("UTF-8"))))
|
||||
|
||||
# Generate a new ActionID
|
||||
action_id = str(uuid.uuid4())
|
||||
drive_data = {
|
||||
"PlantID": plant_id,
|
||||
"ActionID": action_id
|
||||
}
|
||||
|
||||
# Store order in order list or discard if list already contains 5 orders
|
||||
if robot.get_order_number() < 6 and robot.get_robot_status() is True:
|
||||
robot.add_order(drive_data)
|
||||
robot.add_order({"PlantID": [plant_id], "ActionID": action_id})
|
||||
# Send order to robot, if robot is available
|
||||
client.publish(Topics['ROBOT_ACTION_DRIVE'], json.dumps(drive_data))
|
||||
logging.info("BACKEND_ACTION_DRIVE Drive Command published: " + json.dumps(drive_data))
|
||||
else:
|
||||
if robot.get_order_number() < 6:
|
||||
# Add to order list if robot not available
|
||||
robot.add_order(drive_data)
|
||||
logging.info("BACKEND_ACTION_DRIVE New data added to order list: " + str(drive_data))
|
||||
elif robot.get_order_number() >= 6:
|
||||
# Discard order if list full
|
||||
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,
|
||||
robot: Robot):
|
||||
"""
|
||||
This function is called when the frontend sends a drive_all command which lets the robot drive to all registered plants.
|
||||
Same as action_drive(), but for all plants.
|
||||
:param client:
|
||||
:param userdata:
|
||||
:param message:
|
||||
:param mydatabase:
|
||||
:param robot:
|
||||
:return: none
|
||||
"""
|
||||
|
||||
# Get all plantnames from the database and extract the id from them
|
||||
plant_names = mydatabase.get_plant_names()
|
||||
plant_ids = []
|
||||
print(plant_names)
|
||||
|
||||
for names in plant_names:
|
||||
_id = mydatabase.get_plant_id(names)
|
||||
_id = mydatabase.get_plant_id(names[0])
|
||||
plant_ids.append(_id)
|
||||
|
||||
# Create a new order number
|
||||
action_id = str(uuid.uuid4())
|
||||
drive_data = {
|
||||
"PlantID": plant_ids,
|
||||
"ActionID": action_id
|
||||
}
|
||||
print(drive_data)
|
||||
|
||||
# Send drive command to Robot if possible (same as action_drive())
|
||||
if robot.get_order_number() < 6 and robot.get_robot_status() is True:
|
||||
robot.add_order(drive_data)
|
||||
client.publish(Topics['ROBOT_ACTION_DRIVEALL'], json.dumps(plant_ids))
|
||||
client.publish(Topics['ROBOT_ACTION_DRIVEALL'], json.dumps(drive_data))
|
||||
logging.info("BACKEND_ACTION_DRIVEALL Drive Command published: " + json.dumps(drive_data))
|
||||
else:
|
||||
if robot.get_order_number() < 6:
|
||||
@@ -129,63 +274,153 @@ def action_driveall(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, my
|
||||
|
||||
|
||||
def action_getposition(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase):
|
||||
"""
|
||||
This function is called when the frontend demands the robots position from the backend. It forwards the command to
|
||||
the robot.
|
||||
:param client: mqtt client object
|
||||
:param userdata:
|
||||
:param message:
|
||||
:param mydatabase:
|
||||
:return: none
|
||||
"""
|
||||
|
||||
# Send command to robot
|
||||
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):
|
||||
"""
|
||||
This function is called when the frontend demands the robots battery status from the backend. It forwards the
|
||||
command to the robot to get new information.
|
||||
:param client: mqtt client object
|
||||
:param userdata:
|
||||
:param message:
|
||||
:return: none
|
||||
"""
|
||||
|
||||
# Send command to robot
|
||||
client.publish(Topics['ROBOT_ACTION_GETBATTERY'])
|
||||
logging.info("BACKEND_ACTION_GETBATTERY message forwarded to Robot")
|
||||
battery_data = {
|
||||
"Battery": 66,
|
||||
"Timestamp": str(datetime.now())
|
||||
}
|
||||
print(battery_data)
|
||||
client.publish(Topics['BACKEND_DATA_BATTERY'], json.dumps(battery_data))
|
||||
|
||||
|
||||
def action_getalldata(client: mqtt.Client, userdata, message: Union[mqtt.MQTTMessage, list], mydatabase: PlantDataBase):
|
||||
"""
|
||||
This function is called when the frontend demands the last data of the registered plants. It gets the last data from
|
||||
the local database and forwards it to the frontend
|
||||
:param client: mqtt client object
|
||||
:param userdata:
|
||||
:param message:
|
||||
:param mydatabase: database object, where the plant data is stored
|
||||
:return: none
|
||||
"""
|
||||
|
||||
# get the all PlantNames
|
||||
plant_names = mydatabase.get_plant_names()
|
||||
alldata = []
|
||||
|
||||
# Get the latest data from all registered plant names by using the plant names
|
||||
for i in plant_names:
|
||||
alldata.append(mydatabase.get_latest_data(plant_name=i))
|
||||
alldata.append(mydatabase.get_latest_data(plant_name=i[0]))
|
||||
|
||||
# Send the data as a list to the frontends channel
|
||||
client.publish(Topics['BACKEND_DATA_SENSORDATAALL'], json.dumps(alldata))
|
||||
logging.info("BACKEND_DATA_SENSORDATAALL got data from database:" + str(alldata))
|
||||
|
||||
|
||||
def action_newplant(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase):
|
||||
"""
|
||||
This function is called when the frontend wants to register a new plant. The information of the new plant is
|
||||
delivered from the frontend and used to register the plant
|
||||
:param client: mqtt client object
|
||||
:param userdata:
|
||||
:param message: data from the frontend
|
||||
:param mydatabase: local database
|
||||
:return: none
|
||||
"""
|
||||
|
||||
# Load the plant data as json
|
||||
plant_data = json.loads(message.payload.decode("UTF-8"))
|
||||
|
||||
# Insert the plant in the database
|
||||
mydatabase.insert_plant(plantname=plant_data["PlantName"], plant_id=plant_data["PlantID"])
|
||||
|
||||
# Insert a first measurement value in the database
|
||||
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"])
|
||||
logging.info("BACKEND_ACTION_NEWPLANT new plant data received and inserted: " + str(plant_data))
|
||||
|
||||
# Send all new plant data to the frontend to update it
|
||||
action_getalldata(client, userdata, message, mydatabase)
|
||||
|
||||
|
||||
def action_configureplant(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase):
|
||||
"""
|
||||
This function is called when a parameter of a plant is changed by the frontend. It updates the information in the
|
||||
database and sends the updated data to the frontend
|
||||
:param client: mqtt client object
|
||||
:param userdata:
|
||||
:param message: received data from frontend
|
||||
:param mydatabase: local database
|
||||
:return: none
|
||||
"""
|
||||
|
||||
# Load the received data as json
|
||||
plant_data = json.loads(message.payload.decode("UTF-8"))
|
||||
|
||||
# Update the plant in the database
|
||||
mydatabase.configure_plant(plant_id=plant_data["PlantID"], plantname=plant_data["PlantName"])
|
||||
# Insert measurement_data into the database (from frontend)
|
||||
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"])
|
||||
logging.info("BACKEND_ACTION_CONFIGUREPLANT configure plant data received and inserted: " + str(plant_data))
|
||||
|
||||
# Update the frontend with the current data
|
||||
action_getalldata(client, userdata, message, mydatabase)
|
||||
|
||||
|
||||
def action_deleteplant(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase):
|
||||
"""
|
||||
This function is called when the frontend wants to delete a registered plant from the database.
|
||||
:param client:
|
||||
:param userdata:
|
||||
:param message: Received data from the frontend
|
||||
:param mydatabase: local database object
|
||||
:return: none
|
||||
"""
|
||||
|
||||
# Load the Plant-Name from the message
|
||||
delete_plant = json.loads(message.payload.decode("UTF-8"))
|
||||
|
||||
# Delete the plant
|
||||
mydatabase.delete_plant(plant_id=delete_plant)
|
||||
logging.info("BACKEND_ACTION_DELETEPLANT delete plant data received and deleted: " + str(delete_plant))
|
||||
|
||||
# Send current data to frontend to update it
|
||||
action_getalldata(client, userdata, message, mydatabase)
|
||||
|
||||
|
||||
def action_countplants(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase):
|
||||
"""
|
||||
This function is called when the frontend requires the count of the currently registerd plants. It sends the number
|
||||
and maximal possible plant number to the frontend as a json object
|
||||
:param client: mqtt client object
|
||||
:param userdata:
|
||||
:param message:
|
||||
:param mydatabase: local database
|
||||
:return: none
|
||||
"""
|
||||
|
||||
# Count plants
|
||||
count = mydatabase.plant_count()
|
||||
|
||||
# Create Object and send to the FrontEnd
|
||||
count_payload = {
|
||||
"CurrentCount": count,
|
||||
"MaxCount": MAX_PLANT_COUNT
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
"""
|
||||
created by caliskan at 19.04.2023
|
||||
|
||||
contains all constants for the backend architecture of the smart garden project
|
||||
contains all constants for the backend architecture of the smart garden project. This file contains no executable script
|
||||
and is only for documentation purpose
|
||||
"""
|
||||
|
||||
MQTT_BROKER_LOCAL = "192.168.137.197"
|
||||
MQTT_BROKER_LOCAL = "192.168.0.102"
|
||||
MQTT_BROKER_GLOBAL = "mqtt.eclipseprojects.io"
|
||||
RASPI_CLIENT_ID = "smart_farming_raspi"
|
||||
BACKEND_CLIENT_ID = "smart_farming_server"
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
test_dict = {
|
||||
"PlantData": [ {"PlantID":1, "Test": 0}, {"PlantID": 2, "Test": 0}],
|
||||
"ActionID": "SUIIII"
|
||||
}
|
||||
|
||||
|
||||
plant_ids = []
|
||||
for i in test_dict["PlantData"]:
|
||||
plant_ids.append(i["PlantID"])
|
||||
|
||||
print(plant_ids)
|
||||
@@ -21,13 +21,13 @@ 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 _robot: robot object for
|
||||
:param _mydatabase: object, that contains the custom plant database and methods for its usage
|
||||
:param _client: mqtt client object
|
||||
:param _userdata:
|
||||
:param _flags:
|
||||
:param _rc: connection flag
|
||||
:return:
|
||||
:return: none
|
||||
"""
|
||||
if _rc == 0:
|
||||
print("connected")
|
||||
@@ -39,6 +39,9 @@ def on_connect(_client: mqtt.Client, _userdata, _flags, _rc, _mydatabase, _robot
|
||||
_client.message_callback_add(Topics['ROBOT_DATA_SENSORDATA'], lambda client, userdata, message: data_functions.
|
||||
data_sensordata(client, userdata, message, _mydatabase, _robot))
|
||||
|
||||
_client.subscribe(Topics['ROBOT_DATA_ALL'])
|
||||
_client.message_callback_add(Topics['ROBOT_DATA_ALL'], lambda client, userdata, message: data_functions.
|
||||
data_sensordataall(client, userdata, message, _mydatabase, _robot))
|
||||
_client.subscribe(Topics['ROBOT_DATA_POSITION'])
|
||||
_client.message_callback_add(Topics['ROBOT_DATA_POSITION'], data_functions.data_position)
|
||||
|
||||
@@ -97,10 +100,15 @@ def on_connect(_client: mqtt.Client, _userdata, _flags, _rc, _mydatabase, _robot
|
||||
|
||||
|
||||
def main():
|
||||
# Create Robot Object
|
||||
robot = Robot()
|
||||
|
||||
# Connect to Plant_Database and create tables if database did not exist
|
||||
my_database = PlantDataBase(database_name=DATABASE_NAME)
|
||||
my_database.create_tables()
|
||||
mqttclient = mqtt.Client(BACKEND_CLIENT_ID)
|
||||
|
||||
# Create MQTT Client and connect to local broker
|
||||
mqttclient = mqtt.Client(BACKEND_CLIENT_ID, """transport="websockets""""") # transport websockets required for local broker
|
||||
mqttclient.on_connect = lambda client, userdata, flags, rc: on_connect(_client=client,
|
||||
_userdata=userdata,
|
||||
_flags=flags,
|
||||
@@ -108,10 +116,14 @@ def main():
|
||||
_mydatabase=my_database,
|
||||
_robot=robot)
|
||||
mqttclient.connect(MQTT_BROKER_GLOBAL)
|
||||
|
||||
# Initialize logger and save in server.log file
|
||||
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))
|
||||
|
||||
# Starting mqttclient infinite loop
|
||||
mqttclient.loop_forever()
|
||||
|
||||
|
||||
|
||||
@@ -2,30 +2,47 @@
|
||||
import sqlite3
|
||||
from typing import Optional
|
||||
import logging
|
||||
from itertools import chain
|
||||
|
||||
|
||||
class PlantDataBase:
|
||||
"""
|
||||
Class to create Makeathon database
|
||||
Class of a PlantDataBase Object. It contains functions specifically for the plantdatabase.
|
||||
Usage:
|
||||
- First declare object
|
||||
- Then use create_tables() to create the tables (NECESSARY !!)
|
||||
- After that use the methods of this class
|
||||
"""
|
||||
|
||||
def __init__(self, database_name: str):
|
||||
self.db_file = database_name # 'backend_database.db'
|
||||
"""
|
||||
Only Constructor of the Class. Pass name of database to connect to. If not available it will create a new one.
|
||||
:param database_name: Name of the SQLITE database file system
|
||||
"""
|
||||
self.db_file = database_name # 'backend_database.db' usually used name
|
||||
self.conn = None
|
||||
try:
|
||||
# connect or create new database if not available
|
||||
self.conn = sqlite3.connect(self.db_file, check_same_thread=False)
|
||||
except sqlite3.Error as e:
|
||||
logging.error("Database init error: " + str(e))
|
||||
|
||||
# cursor on the database
|
||||
self.cur = self.conn.cursor()
|
||||
|
||||
def create_tables(self):
|
||||
"""
|
||||
Use this method to create the plants and measurement_values tables. Call this function before using the data
|
||||
handling methods below
|
||||
:return: True if successfully, False if not
|
||||
"""
|
||||
try:
|
||||
# Create plants table if not already existing
|
||||
table_config = "CREATE TABLE IF NOT EXISTS plants " \
|
||||
"(PlantID INTEGER PRIMARY KEY," \
|
||||
"PlantName TEXT)"
|
||||
self.cur.execute(table_config)
|
||||
|
||||
# Create measurement_values_table if not already existing
|
||||
table_config = "CREATE TABLE IF NOT EXISTS measurement_values " \
|
||||
"(measurementID INTEGER PRIMARY KEY," \
|
||||
"Timestamp DATETIME DEFAULT (datetime('now', 'localtime'))," \
|
||||
@@ -43,6 +60,12 @@ class PlantDataBase:
|
||||
return False
|
||||
|
||||
def insert_plant(self, plantname: str, plant_id: int) -> bool:
|
||||
"""
|
||||
Insert a new plant with an id and a plantname
|
||||
:param plantname: name of plant
|
||||
:param plant_id: id of plant (position in the bed, must be unique!!)
|
||||
:return: True if successfully, False if not
|
||||
"""
|
||||
try:
|
||||
self.cur.execute("INSERT INTO plants (PlantName, PlantID) VALUES (?,?)", (plantname, plant_id))
|
||||
self.conn.commit()
|
||||
@@ -52,6 +75,12 @@ class PlantDataBase:
|
||||
return False
|
||||
|
||||
def configure_plant(self, plant_id: int, plantname: str) -> bool:
|
||||
"""
|
||||
Change a plants parameters in the database by using its PlantID as a search criteria
|
||||
:param plant_id: id of plant
|
||||
:param plantname: name of plant
|
||||
:return: True if successfully, False if not
|
||||
"""
|
||||
try:
|
||||
self.cur.execute("UPDATE plants SET PlantID = ?, PlantName = ? WHERE PlantID= ?",
|
||||
(plant_id, plantname, plant_id))
|
||||
@@ -62,6 +91,11 @@ class PlantDataBase:
|
||||
return False
|
||||
|
||||
def delete_plant(self, plant_id):
|
||||
"""
|
||||
Delete a plant from the database
|
||||
:param plant_id: PlantID of plant to delete
|
||||
:return: True if successfully, False if not
|
||||
"""
|
||||
try:
|
||||
self.cur.execute('DELETE FROM plants WHERE PlantID = ?', (plant_id,))
|
||||
self.conn.commit()
|
||||
@@ -75,6 +109,15 @@ class PlantDataBase:
|
||||
sensordata_humidity,
|
||||
sensordata_soil_moisture,
|
||||
sensordata_brightness) -> bool:
|
||||
"""
|
||||
Insert a measurement value of plantID
|
||||
:param plant_id: plantID of plant
|
||||
:param sensordata_temp: Temperature
|
||||
:param sensordata_humidity: Air Humidity value
|
||||
:param sensordata_soil_moisture: Soil Moisture value
|
||||
:param sensordata_brightness: brightness value
|
||||
:return: True if successfully, False if not
|
||||
"""
|
||||
try:
|
||||
self.cur.execute(f"INSERT INTO measurement_values (PlantID, AirTemperature, AirHumidity,"
|
||||
f"SoilMoisture, Brightness) VALUES "
|
||||
@@ -88,10 +131,11 @@ class PlantDataBase:
|
||||
|
||||
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:
|
||||
Gets the newest parameter of specific plant and returns all parameters in json format.
|
||||
Either pass plant_name OR plant_id, BOTH passed -> ERROR
|
||||
:param plant_id: PlantID of plant
|
||||
:param plant_name: Name of Plant
|
||||
:return: JSON with data if successfully, else none
|
||||
"""
|
||||
try:
|
||||
if plant_name is not None and plant_id is None:
|
||||
@@ -119,6 +163,11 @@ class PlantDataBase:
|
||||
logging.error("Could not get measurement values: " + str(e))
|
||||
|
||||
def delete_data(self, table_name):
|
||||
"""
|
||||
Delete all data from a specific tabel
|
||||
:param table_name: tabel you want to delete
|
||||
:return: True if successfully, False if not
|
||||
"""
|
||||
try:
|
||||
self.cur.execute(f'DELETE FROM {table_name}')
|
||||
self.conn.commit()
|
||||
@@ -129,7 +178,7 @@ class PlantDataBase:
|
||||
def plant_count(self) -> int:
|
||||
"""
|
||||
returns the number of plants registered in the database
|
||||
:return:
|
||||
:return: Count of registered plants as int
|
||||
"""
|
||||
try:
|
||||
self.cur.execute("SELECT COUNT(*) FROM plants")
|
||||
@@ -138,13 +187,22 @@ class PlantDataBase:
|
||||
logging.error("Could not count plants: " + str(e))
|
||||
|
||||
def get_plant_names(self) -> list:
|
||||
"""
|
||||
Use this method to get a list of the Names of all registered plants
|
||||
:return: list containing plantNames
|
||||
"""
|
||||
try:
|
||||
self.cur.execute("SELECT PlantName FROM plants")
|
||||
return [row[0] for row in self.cur.fetchall()]
|
||||
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:
|
||||
"""
|
||||
Use this method to get the PlantID of a registered plant by its name
|
||||
:param plant_name: name of registered plant
|
||||
:return: ID of plant as int
|
||||
"""
|
||||
try:
|
||||
self.cur.execute("SELECT PlantID FROM plants WHERE PlantName=?", (plant_name,))
|
||||
return self.cur.fetchone()[0]
|
||||
@@ -152,4 +210,7 @@ class PlantDataBase:
|
||||
logging.error("Could not get plant id: " + str(e))
|
||||
|
||||
def __del__(self):
|
||||
"""
|
||||
Destructor of Class. Disconnects from database.
|
||||
"""
|
||||
self.conn.close()
|
||||
@@ -1,7 +1,7 @@
|
||||
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
|
||||
store them instead an instance of this robot object. It saves the orders and some basic information of the robots status
|
||||
"""
|
||||
def __init__(self):
|
||||
self.robot_ready = True
|
||||
|
||||
@@ -65,7 +65,6 @@ ALLSENSORDATA = {
|
||||
"ActionID": ""
|
||||
}
|
||||
|
||||
# TODO When sensor is available
|
||||
POSITION = {
|
||||
"Position": ""
|
||||
}
|
||||
@@ -115,7 +114,6 @@ ALLPLANTDATA = [
|
||||
PLANTDATA
|
||||
]
|
||||
|
||||
# TODO When sensor is available
|
||||
POSITION = {
|
||||
"Position": "",
|
||||
"Timestamp": ""
|
||||
|
||||
@@ -18,6 +18,8 @@ def measure_send_data(plantID, actionID, client: mqtt.Client):
|
||||
"""
|
||||
Reads all sensors and publishes data via MQTT in form of SENSORDATA
|
||||
|
||||
***Function is only neccessary for driving without color codes***
|
||||
|
||||
Args:
|
||||
plantID (_type_): plant to measure
|
||||
actionID (_type_): current ID of driving action
|
||||
@@ -48,7 +50,7 @@ def drive_plant_thread(plantID, actionID, client: mqtt.Client):
|
||||
Meassure and publish data via MQTT
|
||||
Drive home to starting point
|
||||
|
||||
***If color codes are working properly this function is not needed anymore***
|
||||
***Function is only neccessary for driving without color codes***
|
||||
|
||||
Args:
|
||||
plantID (_type_): plant to measure
|
||||
@@ -58,35 +60,38 @@ def drive_plant_thread(plantID, actionID, client: mqtt.Client):
|
||||
|
||||
errorCode = os.system(f'sshpass -p maker ssh robot@ev3dev.local python3 /home/robot/Programme/plant_{plantID}.py')
|
||||
|
||||
# Error handling for drive operation
|
||||
# Abort when drive operation at start when error occurs
|
||||
if errorCode != 0:
|
||||
if errorCode == 65280:
|
||||
errorMessage = "EV3 not connected"
|
||||
else:
|
||||
errorMessage = "Motor or Sensor unplugged"
|
||||
|
||||
logging.info(f"{errorMessage}, Drive Plant aborted, Robot at starting position")
|
||||
logging.error(f"{errorMessage}, Drive Plant aborted, Robot at starting position")
|
||||
client.publish(Topics["ROBOT_DATA_ERROR"], f"{errorMessage}, Drive Plant aborted, Robot at starting position", qos=1)
|
||||
client.publish(Topics["ROBOT_DATA_ROBOTREADY"], "True", qos=1)
|
||||
|
||||
return
|
||||
|
||||
logging.info("Measuring Sensors")
|
||||
measure_send_data(plantID, actionID, client)
|
||||
|
||||
# TODO How to Handle Pictures and PlantID
|
||||
logging.info("Taking Picture")
|
||||
sendPicture(client)
|
||||
# Taking Picture not fully implemented
|
||||
# logging.info("Taking Picture")
|
||||
# sendPicture(client)
|
||||
|
||||
logging.info("Robot driving home")
|
||||
errorCode = os.system(f'sshpass -p maker ssh robot@ev3dev.local python3 /home/robot/Programme/drive_back.py {plantID}')
|
||||
|
||||
# Error handling for drive operation
|
||||
# Abort when drive operation at start when error occurs
|
||||
if errorCode != 0:
|
||||
if errorCode == 65280:
|
||||
errorMessage = "EV3 not connected"
|
||||
else:
|
||||
errorMessage = "Motor or Sensor unplugged"
|
||||
|
||||
logging.info(f"{errorMessage}, Drive Plant aborted, Robot at starting position")
|
||||
logging.error(f"{errorMessage}, Drive Plant aborted, Robot at starting position")
|
||||
client.publish(Topics["ROBOT_DATA_ERROR"], f"{errorMessage}, Drive Plant aborted, Robot at plant {plantID}", qos=1)
|
||||
client.publish(Topics["ROBOT_DATA_ROBOTREADY"], "True", qos=1)
|
||||
return
|
||||
@@ -111,7 +116,7 @@ def drive_plant_all_thread(plantIDs: list, actionID, client: mqtt.Client):
|
||||
client (mqtt.Client): current MQTT client
|
||||
"""
|
||||
|
||||
allPlantData = {
|
||||
allPlantData = { # Sensordata for all plants
|
||||
"SensorData": [],
|
||||
"ActionID": ""
|
||||
}
|
||||
@@ -119,17 +124,18 @@ def drive_plant_all_thread(plantIDs: list, actionID, client: mqtt.Client):
|
||||
allPlantData["ActionID"] = actionID
|
||||
|
||||
for plant in plantIDs:
|
||||
# FIXME Only possible with color codes
|
||||
logging.info(f"Robot Driving to plant {plant}")
|
||||
errorCode = os.system(f'sshpass -p maker ssh robot@ev3dev.local python3 /home/robot/Programme/drive_plant.py {plant}')
|
||||
|
||||
# Error handling for drive operation
|
||||
# Abort when drive operation at start when error occurs
|
||||
if errorCode != 0:
|
||||
if errorCode == 65280:
|
||||
errorMessage = "EV3 not connected"
|
||||
else:
|
||||
errorMessage = "Motor or Sensor unplugged"
|
||||
|
||||
logging.info(f"{errorMessage}, Drive Plant aborted, Robot at starting position")
|
||||
logging.error(f"{errorMessage}, Drive Plant aborted, Robot at starting position")
|
||||
client.publish(Topics["ROBOT_DATA_ERROR"], f"{errorMessage}, Drive Plant aborted, Robot at starting position", qos=1)
|
||||
client.publish(Topics["ROBOT_DATA_ROBOTREADY"], "True", qos=1)
|
||||
return
|
||||
@@ -149,27 +155,32 @@ def drive_plant_all_thread(plantIDs: list, actionID, client: mqtt.Client):
|
||||
allPlantData["SensorData"].append(sensordata)
|
||||
|
||||
|
||||
# TODO How to Handle Pictures and PlantID
|
||||
logging.info("Taking Picture")
|
||||
sendPicture(client)
|
||||
# Taking Picture not fully implemented
|
||||
# logging.info("Taking Picture")
|
||||
# sendPicture(client)
|
||||
|
||||
errorCode = os.system(f'sshpass -p maker ssh robot@ev3dev.local python3 /home/robot/Programme/drive_arm.py {plant}')
|
||||
# Error handling for drive operation
|
||||
# Abort when drive operation at start when error occurs
|
||||
if errorCode != 0:
|
||||
if errorCode == 65280:
|
||||
errorMessage = "EV3 not connected"
|
||||
else:
|
||||
errorMessage = "Motor or Sensor unplugged"
|
||||
|
||||
logging.info(f"{errorMessage}, Drive Plant aborted, Robot at starting position")
|
||||
logging.error(f"{errorMessage}, Drive Plant aborted, Robot at plant {plant}")
|
||||
client.publish(Topics["ROBOT_DATA_ERROR"], f"{errorMessage}, Drive Plant aborted, Robot at plant {plant}", qos=1)
|
||||
client.publish(Topics["ROBOT_DATA_ROBOTREADY"], "True", qos=1)
|
||||
return
|
||||
|
||||
# End of drive and measure operation
|
||||
logging.info("All Plants measured, sending data")
|
||||
client.publish(Topics["ROBOT_DATA_ALL"], json.dumps(allPlantData, indent=4), qos=1)
|
||||
|
||||
|
||||
logging.info("Robot driving home")
|
||||
# Error handling for drive operation
|
||||
# Abort when drive operation at start when error occurs
|
||||
errorCode = os.system(f'sshpass -p maker ssh robot@ev3dev.local python3 /home/robot/Programme/drive_back_straight.py')
|
||||
if errorCode != 0:
|
||||
if errorCode == 65280:
|
||||
@@ -177,7 +188,7 @@ def drive_plant_all_thread(plantIDs: list, actionID, client: mqtt.Client):
|
||||
else:
|
||||
errorMessage = "Motor or Sensor unplugged"
|
||||
|
||||
logging.info(f"{errorMessage}, Drive Plant aborted, Robot at starting position")
|
||||
logging.error(f"{errorMessage}, Drive Plant aborted, Robot at starting position")
|
||||
client.publish(Topics["ROBOT_DATA_ERROR"], f"{errorMessage}, Drive Plant aborted, Robot at plant {plant}", qos=1)
|
||||
client.publish(Topics["ROBOT_DATA_ROBOTREADY"], "True", qos=1)
|
||||
return
|
||||
@@ -196,7 +207,7 @@ def drive_plant(client: mqtt.Client, userdata, message: mqtt.MQTTMessage):
|
||||
Function to drive to plant according to request
|
||||
Starting Drive in Thread to not block main programm
|
||||
|
||||
***If color codes are working properly this function is not needed anymore***
|
||||
***Function is only neccessary for driving without color codes***
|
||||
|
||||
Args:
|
||||
client (mqtt.Client): current mqtt client
|
||||
@@ -215,7 +226,6 @@ def drive_plant(client: mqtt.Client, userdata, message: mqtt.MQTTMessage):
|
||||
thread.start()
|
||||
|
||||
|
||||
# FIXME Only possible with color codes
|
||||
def drive_plant_all(client: mqtt.Client, userdata, message: mqtt.MQTTMessage):
|
||||
"""
|
||||
Function to drive to plants according to request
|
||||
@@ -240,7 +250,7 @@ def drive_plant_all(client: mqtt.Client, userdata, message: mqtt.MQTTMessage):
|
||||
thread.start()
|
||||
|
||||
|
||||
def get_position(clients: mqtt.Client, userdata, message: mqtt.MQTTMessage):
|
||||
def get_position(client: mqtt.Client, userdata, message: mqtt.MQTTMessage):
|
||||
"""
|
||||
Callback function for GPS position request
|
||||
Function to send actual GPS position via MQTT in form of POSITION
|
||||
@@ -254,18 +264,18 @@ def get_position(clients: mqtt.Client, userdata, message: mqtt.MQTTMessage):
|
||||
"""
|
||||
logging.info("Received position request")
|
||||
|
||||
# TODO Write GPS Sensor Function
|
||||
position = {
|
||||
"Position": ""
|
||||
}
|
||||
|
||||
clients.publish(Topics["ROBOT_DATA_POSITION"], json.dumps(position, indent=4), qos=1)
|
||||
client.publish(Topics["ROBOT_DATA_POSITION"], json.dumps(position, indent=4), qos=1)
|
||||
|
||||
|
||||
def get_batteryStatus(client: mqtt.Client, userdata, message: mqtt.MQTTMessage):
|
||||
"""
|
||||
Callback function for battery status request
|
||||
Function to read battery status from EV3 and send via MQTT in form of BATTERY
|
||||
Current battery level is stored in "voltage_now" file
|
||||
Reading battery level via SSH script execution
|
||||
Battery level shown in Volts:
|
||||
8,5V -> 100%
|
||||
@@ -282,7 +292,7 @@ def get_batteryStatus(client: mqtt.Client, userdata, message: mqtt.MQTTMessage):
|
||||
batteryLevel = int(os.popen('sshpass -p maker ssh robot@ev3dev.local cat /sys/devices/platform/battery/power_supply/lego-ev3-battery/voltage_now').read())
|
||||
except:
|
||||
logging.error("EV3 not connected")
|
||||
client.publish(Topics["ROBOT_DATA_ERROR"], "Robot not connected", qos=1)
|
||||
client.publish(Topics["ROBOT_DATA_ERROR"], "EV3 not connected", qos=1)
|
||||
return
|
||||
|
||||
batteryLevel = round(batteryLevel / 1000000, 2) # Conversion to Volt
|
||||
|
||||
@@ -45,7 +45,7 @@ def on_connect(client: mqtt.Client, userdata, flags, rc):
|
||||
|
||||
def main():
|
||||
"""
|
||||
Initialises MQTT and Sensors
|
||||
Initialises MQTT
|
||||
Runs forever and controlls all robot functions
|
||||
"""
|
||||
logging.basicConfig(filename="robot.log", filemode="a", encoding="utf-8", level=logging.DEBUG, format='%(asctime)s %(name)s %(levelname)s %(message)s',
|
||||
|
||||
@@ -42,7 +42,7 @@ def readDHT22():
|
||||
|
||||
def readTSL2561():
|
||||
"""
|
||||
Reads TSL2561 brightness in Lux and returns integer value
|
||||
Reads TSL2561 brightness via I2C in Lux and returns integer value
|
||||
|
||||
Raises:
|
||||
Exception: If TSL2561 not connected properly
|
||||
@@ -67,7 +67,7 @@ def readTSL2561():
|
||||
|
||||
def readMCP3008():
|
||||
"""
|
||||
Reads YL-69 via MCP3008 ADC soil moisture in percent and returns float value
|
||||
Reads YL-69 via MCP3008 ADC and SPI soil moisture in percent and returns float value
|
||||
|
||||
Raises:
|
||||
Exception: If YL-69 not connected properly
|
||||
@@ -99,6 +99,7 @@ def readSensors(sensorData):
|
||||
"""
|
||||
Read DHT22, TSL2561 and Humidity Sensor
|
||||
Dictionary is passed to ensure that values are available when errors occur
|
||||
When error occurs during reading of sensor, affected values are set to 0
|
||||
|
||||
Args:
|
||||
sensorData (dictionary): Dictionary of type SENSORDATA
|
||||
@@ -114,7 +115,7 @@ def readSensors(sensorData):
|
||||
try:
|
||||
sensorData["AirTemperature"], sensorData["AirHumidity"] = readDHT22()
|
||||
except Exception as e:
|
||||
sensorData["AirHumidity"] = 0 # No value returend if error occurs -> setting dummy value
|
||||
sensorData["AirHumidity"] = 0 # No value returend if error occurs -> setting safe values
|
||||
sensorData["AirTemperature"] = 0
|
||||
errorMessage = str(e) + "\n" # Appending received error message to later forward all occured errors
|
||||
|
||||
@@ -122,17 +123,17 @@ def readSensors(sensorData):
|
||||
try:
|
||||
sensorData["Brightness"] = readTSL2561()
|
||||
except Exception as e:
|
||||
sensorData["Brightness"] = 0 # No value returend if error occurs -> setting dummy value
|
||||
sensorData["Brightness"] = 0 # No value returend if error occurs -> setting safe value
|
||||
errorMessage = errorMessage + str(e) + "\n" # Appending received error message to later forward all occured errors
|
||||
|
||||
# read YL-69
|
||||
try:
|
||||
sensorData["SoilMoisture"] = readMCP3008()
|
||||
except Exception as e:
|
||||
sensorData["SoilMoisture"] = 0 # No value returend if error occurs -> setting dummy value
|
||||
sensorData["SoilMoisture"] = 0 # No value returend if error occurs -> setting safe value
|
||||
errorMessage = errorMessage + str(e) + "\n" # Appending received error message to later forward all occured errors
|
||||
|
||||
# raise combined error message
|
||||
# raise combined error message, successfull values still available
|
||||
if errorMessage != "":
|
||||
raise Exception(errorMessage)
|
||||
|
||||
@@ -146,15 +147,13 @@ def takePicture():
|
||||
try:
|
||||
camera = PiCamera()
|
||||
except:
|
||||
# raise Exception("Camera not connected")
|
||||
pass
|
||||
raise Exception("Camera not connected")
|
||||
|
||||
camera.start_preview()
|
||||
camera.capture("picture.png")
|
||||
camera.stop_preview()
|
||||
|
||||
|
||||
# TODO - read position with sensor
|
||||
def readPosition():
|
||||
"""
|
||||
Read and return Position
|
||||
|
||||
Reference in new issue
Block a user