Compare commits

...
12 Commits
43 changed files with 5016 additions and 3140 deletions

No files matched your search

+1 -1
View File
@@ -3,7 +3,7 @@
<component name="WebServers"> <component name="WebServers">
<option name="servers"> <option name="servers">
<webServer id="1b3e9e3f-7394-4cbc-a32d-b95ef2abda52" name="backend"> <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>
<advancedOptions dataProtectionLevel="Private" keepAliveTimeout="0" passiveMode="true" shareSSLContext="true" /> <advancedOptions dataProtectionLevel="Private" keepAliveTimeout="0" passiveMode="true" shareSSLContext="true" />
</advancedOptions> </advancedOptions>
Binary file not shown.
File diff suppressed because it is too large. Load diff
@@ -10,7 +10,7 @@
</div> </div>
</div> </div>
<div class="item-robotText"> <div class="item-robotText">
<div *ngIf="robotStateChange$ | async" id="robotIsOnTour">Robot is on Tour</div> <div *ngIf="!(robotIsReady$ | async)" id="robotIsOnTour">Robot is on Tour</div>
<div *ngIf="!(robotStateChange$ | async)" id="robotIsReady">Robot is Ready</div> <div *ngIf="(robotIsReady$ | async)" id="robotIsReady">Robot is Ready</div>
</div> </div>
</div> </div>
@@ -8,7 +8,8 @@ import { StoreService } from '../Service/store.service';
styleUrls: ['./home.component.css'] styleUrls: ['./home.component.css']
}) })
export class HomeComponent { export class HomeComponent {
robotStateChange$: Observable<boolean> = this.storeService.currentRobotReady; // Aktueller RobotStatus wird subscribed
robotIsReady$: Observable<boolean> = this.storeService.currentRobotReady;
constructor(private storeService: StoreService) { constructor(private storeService: StoreService) {
} }
@@ -15,36 +15,43 @@ export class InputComponent implements OnInit, OnDestroy {
subscription: Subscription; subscription: Subscription;
constructor( constructor(
public service: SpeechService, public speechService: SpeechService,
private mqttRequestService: MqttRequestService private mqttRequestService: MqttRequestService
) { ) {
if (!this.service.initSpeechServiceStarted) { if (!this.speechService.initSpeechServiceStarted) {
this.service.init(); this.speechService.init();
} }
} }
ngOnInit(): void { ngOnInit(): void {
this.subscription = this.service.currentApprovalStageMessage.subscribe(msg => { // subscribe auf die eingegebene Spracheingabe, gibt als response einen string
this.subscription = this.speechService.currentApprovalStageMessage.subscribe(msg => {
this.text = msg; this.text = msg;
if (this.text !== '') { if (this.text !== '') {
this.micIsClicked = false; this.micIsClicked = false;
this.mqttRequestService.publishToINPUT(this.text); this.mqttRequestService.publishToINPUT(this.text); // msg wird hier den Request Service weitergeleitet
} }
}); });
} }
/**
* Beim klick auf das Mikrofon, wird die Spracheingabe gestartet. Beim 2ten klick auf das
* Mikrofon wird die Spracheingabe gestoppt
*/
startStopService() { startStopService() {
//
if (this.micIsClicked) { if (this.micIsClicked) {
this.micIsClicked = false; this.micIsClicked = false;
this.service.stop(); this.speechService.stop(); // Spracheingabe stoppen
} else { } else {
this.micIsClicked = true; this.micIsClicked = true;
this.service.start(); this.speechService.start(); //Spracheingabe starten
} }
} }
ngOnDestroy() { ngOnDestroy() {
this.subscription.unsubscribe() this.subscription.unsubscribe()
this.service.onDestory(); this.speechService.onDestory();
this.text = ''; this.text = '';
} }
@@ -1,6 +1,5 @@
import { Component, OnDestroy, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { MqttRequestService } from 'src/app/Service/Mqtt/mqtt-request.service'; import { MqttRequestService } from 'src/app/Service/Mqtt/mqtt-request.service';
declare var speechSynthesis: any; declare var speechSynthesis: any;
@@ -16,6 +15,7 @@ export class OutputComponent implements OnInit, OnDestroy {
constructor(private mqttRequestService: MqttRequestService) { } constructor(private mqttRequestService: MqttRequestService) { }
ngOnInit(): void { ngOnInit(): void {
// subsribe auf "responseMessage", sie wird jedes mal getriggert wenn eine Spracheingabe statt findet
this.subscription = this.mqttRequestService.responseMessage.subscribe(msg => { this.subscription = this.mqttRequestService.responseMessage.subscribe(msg => {
setTimeout(() => { setTimeout(() => {
this.outputText = msg; this.outputText = msg;
@@ -24,6 +24,11 @@ export class OutputComponent implements OnInit, OnDestroy {
}); });
} }
/**
* Methode spricht das übergeben string aus --> Sprachausgabe
* @method speak
* @param string
*/
speak(text: string) { speak(text: string) {
const utterance = new SpeechSynthesisUtterance(text); const utterance = new SpeechSynthesisUtterance(text);
utterance.lang = 'en-US'; utterance.lang = 'en-US';
@@ -34,5 +39,4 @@ export class OutputComponent implements OnInit, OnDestroy {
this.subscription.unsubscribe(); this.subscription.unsubscribe();
this.mqttRequestService.onDestroy(); this.mqttRequestService.onDestroy();
} }
} }
@@ -7,8 +7,13 @@ declare var webkitSpeechRecognition: any;
providedIn: 'root' providedIn: 'root'
}) })
export class SpeechService { export class SpeechService {
/*
* currentApprovalStageMessage wird als observable festgelegt,
* damit man aus anderen Komponenten die änderungen mitbekommt!
*/
private approvalStageMessage = new BehaviorSubject(''); private approvalStageMessage = new BehaviorSubject('');
currentApprovalStageMessage = this.approvalStageMessage.asObservable(); currentApprovalStageMessage = this.approvalStageMessage.asObservable();
initSpeechServiceStarted: boolean = false; initSpeechServiceStarted: boolean = false;
recognition = new webkitSpeechRecognition(); recognition = new webkitSpeechRecognition();
@@ -18,8 +23,6 @@ export class SpeechService {
tempWords: string | undefined; tempWords: string | undefined;
constructor() { constructor() {
this.recognition.pitch = 10;
this.recognition.rate = 3;
} }
init() { init() {
@@ -33,22 +36,19 @@ export class SpeechService {
.map((result) => result.transcript) .map((result) => result.transcript)
.join(''); .join('');
this.tempWords = transcript; this.tempWords = transcript;
console.log('init:' + transcript);
}); });
} }
start() { start() {
this.text = ''; this.text = '';
this.isStoppedSpeechRecog = false; this.isStoppedSpeechRecog = false;
this.recognition.start(); this.recognition.start(); // Start der Sprachaufnahme
console.log("Speech recognition started")
this.recognition.addEventListener('end', () => { this.recognition.addEventListener('end', () => {
this.wordConcat() this.wordConcat()
this.recognition.stop(); this.recognition.stop(); // Ende der Sprachaufnahme
if (!this.isStoppedSpeechRecog) { if (!this.isStoppedSpeechRecog) {
this.isStoppedSpeechRecog = true; this.isStoppedSpeechRecog = true;
console.log("End speech recognition"); this.approvalStageMessage.next(this.text); // String was eingelesen wurde, wird mit der Methode "next(..)" an alle subsriber weitergeleitet!
this.approvalStageMessage.next(this.text);
} }
}); });
} }
@@ -62,6 +62,7 @@ export class SpeechService {
} }
} }
// addiert die Wörter bei der Sprachaufnahme zusammen
wordConcat() { wordConcat() {
this.text = this.text + ' ' + this.tempWords; this.text = this.text + ' ' + this.tempWords;
this.tempWords = ''; this.tempWords = '';
@@ -24,8 +24,8 @@
</div> </div>
</div> </div>
<div class="item-IMG"> <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/Pflanze.png" 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">
</div> </div>
<div class="item-delete"> <div class="item-delete">
<button class="item-Configure" mat-icon-button color="primary" (click)="onConfigurePlant()"> <button class="item-Configure" mat-icon-button color="primary" (click)="onConfigurePlant()">
@@ -18,10 +18,19 @@ export class PlantCardComponent {
constructor(public dialog: MatDialog, private mqttRequestService: MqttRequestService) { constructor(public dialog: MatDialog, private mqttRequestService: MqttRequestService) {
} }
/**
* Methode wird in der HTML datei verwendet damit der Wert nicht undefined ist.
* @method
*/
getFormattedLastRequestedTimestamp(): Observable<string> { getFormattedLastRequestedTimestamp(): Observable<string> {
return of(this.calculateTimestamp(this.plant.Timestamp)) return of(this.calculateTimestamp(this.plant.Timestamp))
} }
/**
* Methode dividiert den Timestamp der Pflanze mit der aktuellen Zeit. Die Zeit wird dann in Tagen, Stunden, Minuten, Sekunden angezeigt
* @method
* @return string wie z.B "1 hour ago"
*/
calculateTimestamp(timestamp: string): string { calculateTimestamp(timestamp: string): string {
const datePlant = new Date(timestamp); const datePlant = new Date(timestamp);
this.currentDate = new Date(); this.currentDate = new Date();
@@ -43,6 +52,10 @@ export class PlantCardComponent {
} }
} }
/**
* Methode öffnet den Dialog "DeleteConfirmDialogComponent"
* @method
*/
onDeletePlant() { onDeletePlant() {
this.dialog.open(DeleteConfirmDialogComponent, { this.dialog.open(DeleteConfirmDialogComponent, {
data: this.plant, data: this.plant,
@@ -50,6 +63,10 @@ export class PlantCardComponent {
}); });
} }
/**
* Methode öffnet den Dialog "ConfigurePlantDialogComponent"
* @method
*/
onConfigurePlant() { onConfigurePlant() {
this.dialog.open(ConfigurePlantDialogComponent, { this.dialog.open(ConfigurePlantDialogComponent, {
data: { data: {
@@ -58,5 +75,4 @@ export class PlantCardComponent {
width: '400px', width: '400px',
}); });
} }
} }
@@ -19,8 +19,8 @@ export class PlantsComponent {
filteredOptions: Observable<string[]>; filteredOptions: Observable<string[]>;
plants$: Observable<Plant[]> = this.storeService.currentPlants; plants$: Observable<Plant[]> = this.storeService.currentPlants;
constructor(public dialog: MatDialog, public storeService: StoreService, private mqttRequestService: MqttRequestService) { 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 => { this.options$.subscribe(response => {
this.filteredOptions = this.myControl.valueChanges.pipe( this.filteredOptions = this.myControl.valueChanges.pipe(
startWith(''), startWith(''),
@@ -30,16 +30,29 @@ export class PlantsComponent {
}) })
} }
/**
* es wird nach dem Pflanzen Namen gesucht.
* @method
* @return Pflant
*/
onSearchPlant(plantName: string): Observable<Plant> { onSearchPlant(plantName: string): Observable<Plant> {
return this.storeService.getPlant(plantName); return this.storeService.getPlant(plantName);
} }
/**
* Methode öffnet das Dialog "AddPlantDialogComponent"
* @method
*/
onNewPlant() { onNewPlant() {
this.dialog.open(AddPlantDialogComponent, { this.dialog.open(AddPlantDialogComponent, {
width: '420px', width: '420px',
}); });
} }
/**
* die ausgewählte Pflanze wird in "selectedPlant" geschrieben
* @method
*/
onOptionSelect(value: string) { onOptionSelect(value: string) {
this.selectedPlant = value; this.selectedPlant = value;
} }
@@ -1,7 +1,7 @@
<div class="container" <div class="container"
*ngIf="robotBatteryResponse$ !== undefined && (robotBatteryResponse$ |async) !== undefined; else errorLoadingData"> *ngIf="robotBatteryResponse$ !== undefined && (robotBatteryResponse$ |async) !== undefined; else errorLoadingData">
<div class="robotImage"> <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>
<div class="robotAkku"> <div class="robotAkku">
<h1 class="akkuMsg">Robot Akku:</h1> <h1 class="akkuMsg">Robot Akku:</h1>
@@ -17,18 +17,32 @@ export class RobotComponent {
constructor(private storeService: StoreService) { constructor(private storeService: StoreService) {
} }
/**
* Methode setzt die Farbe der LoadingBar auf Rot ("warn") oder Blau ("Primary")
* @method
* @return warn oder primary
*/
getLoadingBarColor(): Observable<ThemePalette> { getLoadingBarColor(): Observable<ThemePalette> {
return this.batteryChargingStatus$.pipe( return this.batteryChargingStatus$.pipe(
map(chargingStatus => chargingStatus < 20 ? "warn" : "primary") map(chargingStatus => chargingStatus < 20 ? "warn" : "primary")
) )
} }
/**
* Methode wird in der HTML datei verwendet damit der Wert nicht undefined ist.
* @method
*/
getFormattedLastRequestedBatteryState(): Observable<string> { getFormattedLastRequestedBatteryState(): Observable<string> {
return this.requestTimestamp$.pipe( return this.requestTimestamp$.pipe(
map(timestamp => this.formatLastRequestedBatteryState(timestamp)) map(timestamp => this.formatLastRequestedBatteryState(timestamp))
) )
} }
/**
* Methode dividiert den Timestamp der Pflanze mit der aktuellen Zeit. Die Zeit wird dann in Tagen, Stunden, Minuten, Sekunden angezeigt
* @method
* @return string wie z.B "1 hour ago"
*/
formatLastRequestedBatteryState(timestamp: string): string { formatLastRequestedBatteryState(timestamp: string): string {
const datePlant = new Date(timestamp); const datePlant = new Date(timestamp);
const currentDate = new Date(); const currentDate = new Date();
@@ -49,5 +63,4 @@ export class RobotComponent {
return `${seconds} second${seconds !== 1 ? 's' : ''} ago`; return `${seconds} second${seconds !== 1 ? 's' : ''} ago`;
} }
} }
} }
@@ -1,10 +1,10 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { BehaviorSubject, Subscription } from 'rxjs'; import { BehaviorSubject, Subscription } from 'rxjs';
import { take } from 'rxjs/operators';
import { ErrorDialogComponent } from 'src/app/dialog/error-dialog/error-dialog.component'; import { ErrorDialogComponent } from 'src/app/dialog/error-dialog/error-dialog.component';
import { Plant } from 'src/app/models/plant.model'; import { Plant } from 'src/app/models/plant.model';
import { PlantCount } from 'src/app/models/plantCount.model'; import { PlantCount } from 'src/app/models/plantCount.model';
import { Position } from 'src/app/models/position.model';
import { RobotBattery } from 'src/app/models/robotBattery.model'; import { RobotBattery } from 'src/app/models/robotBattery.model';
import { StoreService } from '../store.service'; import { StoreService } from '../store.service';
import { MqttSetDataService } from './mqtt-set-data.service'; import { MqttSetDataService } from './mqtt-set-data.service';
@@ -14,6 +14,7 @@ import { MqttService } from './mqtt.service';
providedIn: 'root' providedIn: 'root'
}) })
export class MqttRequestService { export class MqttRequestService {
// wird für die Sprachausgabe verwendet
private sourceMessage = new BehaviorSubject(''); private sourceMessage = new BehaviorSubject('');
responseMessage = this.sourceMessage.asObservable(); responseMessage = this.sourceMessage.asObservable();
@@ -22,6 +23,7 @@ export class MqttRequestService {
constructor(private mqttService: MqttService, constructor(private mqttService: MqttService,
private mqttSetDataService: MqttSetDataService, private mqttSetDataService: MqttSetDataService,
private storeService: StoreService, public dialog: MatDialog) { private storeService: StoreService, public dialog: MatDialog) {
//Subscribe to the Topics //Subscribe to the Topics
this.mqttService.subscribeToTopic("BACKEND/DATA/SENSORDATA_ALL").subscribe(data => { this.mqttService.subscribeToTopic("BACKEND/DATA/SENSORDATA_ALL").subscribe(data => {
if (typeof data !== "object") { if (typeof data !== "object") {
@@ -36,11 +38,10 @@ export class MqttRequestService {
console.error('Error:', err); console.error('Error:', err);
}); });
this.mqttService.subscribeToTopic("BACKEND/DATA/ERROR").subscribe(data => { this.mqttService.subscribeToTopic("BACKEND/DATA/ERROR").subscribe(errorMsg => {
if (typeof data !== "object") { if (typeof errorMsg !== "object") {
const payload = JSON.parse(data) as string;
this.dialog.open(ErrorDialogComponent, { this.dialog.open(ErrorDialogComponent, {
data: payload, data: errorMsg,
width: '400px', width: '400px',
}); });
} }
@@ -65,19 +66,6 @@ export class MqttRequestService {
console.error('Error:', err); 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 => { this.mqttService.subscribeToTopic("BACKEND/DATA/BATTERY").subscribe(data => {
if (typeof data !== "object") { if (typeof data !== "object") {
const payload = JSON.parse(data) as RobotBattery; const payload = JSON.parse(data) as RobotBattery;
@@ -92,9 +80,10 @@ export class MqttRequestService {
}); });
this.mqttService.subscribeToTopic("BACKEND/DATA/ROBOTREADY").subscribe(data => { this.mqttService.subscribeToTopic("BACKEND/DATA/ROBOTREADY").subscribe(data => {
if (typeof data !== "object") { if (data === "False") {
const payload = JSON.parse(data) as boolean; this.mqttSetDataService.setRobotReady(false);
this.mqttSetDataService.setRobotReady(payload); } else if (data === "True") {
this.mqttSetDataService.setRobotReady(true);
} }
}, err => { }, err => {
this.dialog.open(ErrorDialogComponent, { this.dialog.open(ErrorDialogComponent, {
@@ -116,29 +105,6 @@ export class MqttRequestService {
}); });
console.error('Error:', err); console.error('Error:', err);
}); });
// this.mqttService.subscribeToTopic("BACKEND/DATA/POSITION").subscribe(data => {
// if (typeof data !== "object") {
// const payload = JSON.parse(data) as Plant[];
// this.mqttSetDataService.setDataAllPlants(payload);
// }
// }, err => {
// 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 //Publish to the Topic to recieve the first Data
this.publishToPLANTCOUNT(); this.publishToPLANTCOUNT();
@@ -150,18 +116,14 @@ export class MqttRequestService {
publishToGETALLDATA() { publishToGETALLDATA() {
this.mqttService.publishToTopic('BACKEND/ACTION/GETALLDATA', JSON.stringify(this.storeService.getAllPlantNames())); this.mqttService.publishToTopic('BACKEND/ACTION/GETALLDATA', JSON.stringify(this.storeService.getAllPlantNames()));
} }
publishToDRIVE() {
this.mqttService.publishToTopic('BACKEND/ACTION/DRIVE');
}
publishToGETBATTERY() { publishToGETBATTERY() {
this.mqttService.publishToTopic('BACKEND/ACTION/GETBATTERY'); this.mqttService.publishToTopic('BACKEND/ACTION/GETBATTERY');
} }
publishToROBOTREADY() { publishToROBOTREADY() {
this.mqttService.publishToTopic('BACKEND/ACTION/ROBOTREADY'); this.mqttService.publishToTopic('BACKEND/ACTION/ROBOTREADY');
} }
publishToGETPOSITION() {
this.mqttService.publishToTopic('BACKEND/ACTION/GETPOSITION');
}
publishToNEWPLANT(plant: Plant) { publishToNEWPLANT(plant: Plant) {
this.mqttService.publishToTopic('BACKEND/ACTION/NEWPLANT', JSON.stringify(plant)); this.mqttService.publishToTopic('BACKEND/ACTION/NEWPLANT', JSON.stringify(plant));
@@ -179,9 +141,15 @@ export class MqttRequestService {
this.mqttService.publishToTopic('BACKEND/ACTION/PLANTCOUNT'); 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
* die Sprachausgabe mit next(..) triggert
* @method
*/
publishToINPUT(message: string) { publishToINPUT(message: string) {
message = message.replace(/\s/g, ""); message = message.replace(/\s/g, ""); //entfernt alle leerzeichen
console.log(":" + message + ":"); console.log(":" + message + ":"); //Nachricht die übermittel wurde, wird ausgegeben auf der Konsole
let msgFound: boolean = false; let msgFound: boolean = false;
if (message === "whereismyrobot") { if (message === "whereismyrobot") {
@@ -194,10 +162,11 @@ export class MqttRequestService {
this.sourceMessage.next("The data is being collected and you can view it under the 'Robot' tab"); this.sourceMessage.next("The data is being collected and you can view it under the 'Robot' tab");
} else if (message === "howaremyplants") { } else if (message === "howaremyplants") {
msgFound = true; msgFound = true;
// this.mqttService.publishToTopic('BACKEND/ACTION/DRIVEALL'); this.mqttService.publishToTopic('BACKEND/ACTION/DRIVEALL');
this.sourceMessage.next("Robot is driving to all plants"); this.sourceMessage.next("Robot is driving to all plants");
} else { } else {
this.storeService.getAllPlantNames().subscribe(result => // schickt den Roboter zur eingegeben Pflanze..
this.storeService.getAllPlantNames().pipe(take(1)).subscribe(result =>
result.forEach((element: string) => { result.forEach((element: string) => {
let msgPlantName = "howismyplant".concat(element.toLowerCase()) let msgPlantName = "howismyplant".concat(element.toLowerCase())
if (message === msgPlantName) { if (message === msgPlantName) {
@@ -210,7 +179,9 @@ export class MqttRequestService {
) )
} }
// Wenn kein case eintrifft, wird "Please try again" ausgegeben
if (!msgFound) { if (!msgFound) {
msgFound = false;
this.sourceMessage.next("Please try again"); this.sourceMessage.next("Please try again");
} }
@@ -32,5 +32,4 @@ export class MqttSetDataService {
setPlantCount(data: PlantCount): void { setPlantCount(data: PlantCount): void {
this.storeService.setPlantCount(data); this.storeService.setPlantCount(data);
} }
} }
@@ -6,12 +6,12 @@ import { Observable } from 'rxjs';
providedIn: 'root' providedIn: 'root'
}) })
export class MqttService { export class MqttService {
private client: MqttClient; private client: MqttClient;
// Connection zu dem MQTT Broker
constructor() { constructor() {
this.client = connect('wss://mqtt.eclipseprojects.io:443/mqtt'); //Je nachdem welchen Link der Broker hat this.client = connect('wss://mqtt.eclipseprojects.io:443/mqtt'); //Online Broker
// this.client = connect('mqtt://192.168.137.197:1883', { clientId: 'kemal' }); // this.client = connect('mqtt://192.168.137.197:1883', { clientId: 'kemal' }); //lokaler Broker
} }
public subscribeToTopic(topic: string): Observable<any> { public subscribeToTopic(topic: string): Observable<any> {
@@ -8,6 +8,8 @@ import { RobotBattery } from '../models/robotBattery.model';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class StoreService { export class StoreService {
positionRobot: Position; positionRobot: Position;
plantCount: PlantCount; plantCount: PlantCount;
@@ -28,7 +28,6 @@
<img class="img-item" src="assets/images/logo.png" alt="not available"> <img class="img-item" src="assets/images/logo.png" alt="not available">
</header> </header>
<body> <body>
<router-outlet></router-outlet> <router-outlet></router-outlet>
</body> </body>
@@ -11,6 +11,7 @@ import { MqttRequestService } from './Service/Mqtt/mqtt-request.service';
export class AppComponent { export class AppComponent {
header: string; header: string;
constructor(private mqttRequestService: MqttRequestService, private location: Location) { constructor(private mqttRequestService: MqttRequestService, private location: Location) {
// Header in der Leiste zu bestimmen
const actualRoute = this.location.path().slice(1); const actualRoute = this.location.path().slice(1);
const lastParam = actualRoute.charAt(0).toUpperCase() + actualRoute.slice(1); const lastParam = actualRoute.charAt(0).toUpperCase() + actualRoute.slice(1);
if (lastParam === "") { if (lastParam === "") {
@@ -17,13 +17,11 @@
Soil Moisture: <br> Soil Moisture: <br>
<img class="icons-measurements" src="assets/images/Icons-Card/brightness_icon.png" alt="not available"> <img class="icons-measurements" src="assets/images/Icons-Card/brightness_icon.png" alt="not available">
Brightness:<br> Brightness:<br>
<!-- <img class="icons-measurements" src="assets/images/Icons-Card/Timestamp_icon.png" alt="not available">
Timestamp: -->
</div> </div>
<div class="item-Measurements-Values"> <div class="item-Measurements-Values">
<mat-form-field class="item-option" appearance="fill"> <mat-form-field class="item-option" appearance="fill">
<mat-label>Choose an ID</mat-label> <mat-label>Choose an ID</mat-label>
<mat-select [(value)]="selected" (closed)="onOptionSelected(selected)"> <mat-select [(value)]="selected" (closed)="onOptionSelected()">
<mat-option *ngFor="let option of options$ | async" [value]="option"> <mat-option *ngFor="let option of options$ | async" [value]="option">
{{option}} {{option}}
</mat-option> </mat-option>
@@ -12,13 +12,17 @@ import { MapPlantDialogComponent } from '../map-plant-dialog/map-plant-dialog.co
templateUrl: './add-plant-dialog.component.html', templateUrl: './add-plant-dialog.component.html',
styleUrls: ['./add-plant-dialog.component.css'] styleUrls: ['./add-plant-dialog.component.css']
}) })
/**
* @description
* Mit diesem Dialog "AddPlantDialogComponent" wird eine neue Pflanze erstellt und in das Backend geschickt.
*/
export class AddPlantDialogComponent implements OnInit { export class AddPlantDialogComponent implements OnInit {
selected: number; selected: number;
options$: Observable<number[]>; options$: Observable<number[]>;
plantNames: string[]; plantNames: string[];
plant = {} as Plant; plant = {} as Plant;
buttonSavedisabled: boolean; buttonSavedisabled: boolean;
number: number[];
constructor(public dialogRef: MatDialogRef<AddPlantDialogComponent>, constructor(public dialogRef: MatDialogRef<AddPlantDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any, @Inject(MAT_DIALOG_DATA) public data: any,
@@ -28,16 +32,26 @@ export class AddPlantDialogComponent implements OnInit {
} }
ngOnInit(): void { ngOnInit(): void {
this.buttonSavedisabled = true; this.buttonSavedisabled = true; //Button "Save" wird disabled
this.mqttRequestService.publishToPLANTCOUNT(); this.mqttRequestService.publishToPLANTCOUNT(); // Pflanzen anzahl wird angefordet#
// ID's bei denen noch keine Pflanze sind, werden in "option$" rein geschrieben
this.storeService.currentPlants.subscribe(response => { this.storeService.currentPlants.subscribe(response => {
this.options$ = this.storeService.getIDsWithoutPlant(response); this.options$ = this.storeService.getIDsWithoutPlant(response);
}) })
// Alle aktuellen Pflanzen Namen werden in "plantNames" geschrieben
this.storeService.getAllPlantNames().subscribe(result => { this.storeService.getAllPlantNames().subscribe(result => {
this.plantNames = result this.plantNames = result
}); });
} }
/**
* Nach dem der Button "Save" geklickt wurde, werden die Daten in die Pflanze geschrieben
* die anschließend geprüft wird auf zulässige Werte, falls unzulässige werte vorhanden sind,
* wird eine Fehlermeldung angezeigt
* @method onSaveDialog
*/
onSaveDialog() { onSaveDialog() {
this.plant.PlantName = (<HTMLInputElement>document.getElementById("inputName")).value; this.plant.PlantName = (<HTMLInputElement>document.getElementById("inputName")).value;
this.plant.AirTemperature = +(<HTMLInputElement>document.getElementById("inputAirTemperature")).value; this.plant.AirTemperature = +(<HTMLInputElement>document.getElementById("inputAirTemperature")).value;
@@ -48,6 +62,7 @@ export class AddPlantDialogComponent implements OnInit {
const plantInputString: string = this.plantInputTestFails(); const plantInputString: string = this.plantInputTestFails();
// bei unzulässigen Werten --> Dialog, bei zulässigen Wertden --> Pflanze wird an das Backend geschickt
if (plantInputString) { if (plantInputString) {
this.dialog.open(ErrorDialogComponent, { this.dialog.open(ErrorDialogComponent, {
data: "Error: " + plantInputString, data: "Error: " + plantInputString,
@@ -55,11 +70,16 @@ export class AddPlantDialogComponent implements OnInit {
}); });
} else { } else {
this.mqttRequestService.publishToNEWPLANT(this.plant); this.mqttRequestService.publishToNEWPLANT(this.plant);
this.mqttRequestService.publishToGETALLDATA(); this.mqttRequestService.publishToGETALLDATA(); // Daten werden aus dem Backend angefragt
window.location.reload(); window.location.reload(); // Seite neu laden
} }
} }
/**
* Methode prüft ob die Werte die eingegeben wurden, korrekt sind.
* @method
* @return Bei einem Fehler --> Fehlermeldung; wenn es passt --> string oder leeren string
*/
plantInputTestFails(): string { plantInputTestFails(): string {
if (this.plantNames.indexOf(this.plant.PlantName) > -1) { if (this.plantNames.indexOf(this.plant.PlantName) > -1) {
@@ -77,12 +97,16 @@ export class AddPlantDialogComponent implements OnInit {
if (this.plant.SoilMoisture > 100) { if (this.plant.SoilMoisture > 100) {
return "Soil Moisture is to high"; return "Soil Moisture is to high";
} }
if (this.plant.Brightness > 100) { if (this.plant.Brightness > 40000) {
return "Brightness is to high"; return "Brightness is to high";
} }
return ""; //Empty string is a Boolean false! return ""; //Empty string is a Boolean false!
} }
/**
* beim klick auf den Button, wird das Dialog "MapPlantDialogComponent" geöffnet
* @method
*/
onOpenMap() { onOpenMap() {
this.dialog.open(MapPlantDialogComponent, { this.dialog.open(MapPlantDialogComponent, {
width: '400px', width: '400px',
@@ -90,11 +114,11 @@ export class AddPlantDialogComponent implements OnInit {
}); });
} }
onOptionSelected(numb: number) { /**
let num: number = numb; * nach der Auswahl der ID, wird der Button "Save" enabled
if (num < 7 && num > 0) { //TODOO anzahl der Pflanzen IDS variable machen statt 7 evtl. eine variable * @method
this.buttonSavedisabled = false; */
} onOptionSelected() {
this.buttonSavedisabled = false;
} }
} }
@@ -1,4 +1,4 @@
import { Component, Inject, OnInit } from '@angular/core'; import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog';
import { MqttRequestService } from 'src/app/Service/Mqtt/mqtt-request.service'; import { MqttRequestService } from 'src/app/Service/Mqtt/mqtt-request.service';
import { Plant } from 'src/app/models/plant.model'; import { Plant } from 'src/app/models/plant.model';
@@ -9,7 +9,7 @@ import { ErrorDialogComponent } from '../error-dialog/error-dialog.component';
templateUrl: './configure-plant-dialog.component.html', templateUrl: './configure-plant-dialog.component.html',
styleUrls: ['./configure-plant-dialog.component.css'] styleUrls: ['./configure-plant-dialog.component.css']
}) })
export class ConfigurePlantDialogComponent implements OnInit { export class ConfigurePlantDialogComponent {
plant: Plant; plant: Plant;
constructor(public dialogRef: MatDialogRef<ConfigurePlantDialogComponent>, constructor(public dialogRef: MatDialogRef<ConfigurePlantDialogComponent>,
@@ -19,9 +19,12 @@ export class ConfigurePlantDialogComponent implements OnInit {
this.plant = data.plant; this.plant = data.plant;
} }
ngOnInit(): void { /**
} * Nach dem der Button "Save" geklickt wurde, werden die Daten in die Pflanze geschrieben
* die anschließend geprüft wird auf zulässige Werte, falls unzulässige werte vorhanden sind,
* wird eine Fehlermeldung angezeigt
* @method onSaveDialog
*/
onSaveDialog() { onSaveDialog() {
this.plant.AirTemperature = +(<HTMLInputElement>document.getElementById("inputAirTemperature")).value; this.plant.AirTemperature = +(<HTMLInputElement>document.getElementById("inputAirTemperature")).value;
this.plant.AirHumidity = +(<HTMLInputElement>document.getElementById("inputAirHumidity")).value; this.plant.AirHumidity = +(<HTMLInputElement>document.getElementById("inputAirHumidity")).value;
@@ -29,19 +32,25 @@ export class ConfigurePlantDialogComponent implements OnInit {
this.plant.Brightness = +(<HTMLInputElement>document.getElementById("inputBrightness")).value; this.plant.Brightness = +(<HTMLInputElement>document.getElementById("inputBrightness")).value;
const plantInputString: string = this.plantInputTestFails(); const plantInputString: string = this.plantInputTestFails();
// bei unzulässigen Werten --> Dialog, bei zulässigen Wertden --> Pflanze wird an das Backend geschickt
if (plantInputString) { if (plantInputString) {
this.dialog.open(ErrorDialogComponent, { this.dialog.open(ErrorDialogComponent, {
data: "Error: " + plantInputString, data: "Error: " + plantInputString,
width: '400px', width: '400px',
}); });
} else { } else {
//Request to the Backend, to change the data of the plant this.mqttRequestService.publishToCONFIGUREPLANT(this.plant); //Request to the Backend, to change the data of the plant
this.mqttRequestService.publishToCONFIGUREPLANT(this.plant); this.mqttRequestService.publishToGETALLDATA(); // Daten werden aus dem Backend angefragt
this.mqttRequestService.publishToGETALLDATA(); window.location.reload(); // Seite neu laden
window.location.reload();
} }
} }
/**
* Methode prüft ob die Werte die eingegeben wurden, korrekt sind.
* @method
* @return Bei einem Fehler --> Fehlermeldung; wenn es passt --> string oder leeren string
*/
plantInputTestFails(): string { plantInputTestFails(): string {
if (this.plant.AirTemperature > 100) { if (this.plant.AirTemperature > 100) {
return "Air Temperature is to high"; return "Air Temperature is to high";
@@ -52,7 +61,7 @@ export class ConfigurePlantDialogComponent implements OnInit {
if (this.plant.SoilMoisture > 100) { if (this.plant.SoilMoisture > 100) {
return "Soil Moisture is to high"; return "Soil Moisture is to high";
} }
if (this.plant.Brightness > 100) { if (this.plant.Brightness > 40000) {
return "Brightness is to high"; return "Brightness is to high";
} }
return ""; //Empty string is a Boolean false! return ""; //Empty string is a Boolean false!
@@ -11,12 +11,15 @@ import { ConfigurePlantDialogComponent } from '../configure-plant-dialog/configu
}) })
export class DeleteConfirmDialogComponent { export class DeleteConfirmDialogComponent {
plant: Plant; plant: Plant;
constructor(public dialogRef: MatDialogRef<ConfigurePlantDialogComponent>, constructor(public dialogRef: MatDialogRef<ConfigurePlantDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: Plant, private mqttRequestService: MqttRequestService) { @Inject(MAT_DIALOG_DATA) public data: Plant, private mqttRequestService: MqttRequestService) {
this.plant = data; this.plant = data;
} }
/**
* beim Click auf "Delete" wird eine anfrage an das Backend geschickt um die Pflanze zu löschen.
* @method
*/
onDelete() { onDelete() {
this.mqttRequestService.publishToDELETEPLANT(this.plant.PlantID); this.mqttRequestService.publishToDELETEPLANT(this.plant.PlantID);
} }
@@ -1,4 +1,4 @@
import { Component, Inject, OnInit } from '@angular/core'; import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { ConfigurePlantDialogComponent } from '../configure-plant-dialog/configure-plant-dialog.component'; import { ConfigurePlantDialogComponent } from '../configure-plant-dialog/configure-plant-dialog.component';
@@ -7,19 +7,15 @@ import { ConfigurePlantDialogComponent } from '../configure-plant-dialog/configu
templateUrl: './error-dialog.component.html', templateUrl: './error-dialog.component.html',
styleUrls: ['./error-dialog.component.css'] styleUrls: ['./error-dialog.component.css']
}) })
export class ErrorDialogComponent implements OnInit { export class ErrorDialogComponent {
text: string = ""; text: string = "";
constructor(public dialogRef: MatDialogRef<ConfigurePlantDialogComponent>, constructor(public dialogRef: MatDialogRef<ConfigurePlantDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any) { @Inject(MAT_DIALOG_DATA) public data: any) {
this.text = data; this.text = data;
} }
ngOnInit(): void {
}
onClose() { onClose() {
window.location.reload(); window.location.reload(); // Seite neu laden
} }
} }
@@ -1,6 +1,6 @@
<div class="container"> <div class="container">
<p class="item-header" mat-dialog-title>Map</p> <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> <div class="item-button" mat-dialog-actions>
<button mat-button mat-dialog-close>Close</button> <button mat-button mat-dialog-close>Close</button>
</div> </div>
@@ -1,15 +1,10 @@
import { Component, OnInit } from '@angular/core'; import { Component } from '@angular/core';
@Component({ @Component({
selector: 'app-map-plant-dialog', selector: 'app-map-plant-dialog',
templateUrl: './map-plant-dialog.component.html', templateUrl: './map-plant-dialog.component.html',
styleUrls: ['./map-plant-dialog.component.css'] styleUrls: ['./map-plant-dialog.component.css']
}) })
export class MapPlantDialogComponent implements OnInit { export class MapPlantDialogComponent {
constructor() { } constructor() { }
ngOnInit(): void {
}
} }
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.
+250 -15
View File
@@ -3,6 +3,7 @@ created by caliskan at 19.04.2023
This file contains all functions, which handle the different cases. This file contains all functions, which handle the different cases.
Every function should return json format with the wanted data from the database 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 import paho.mqtt.client as mqtt
from plantdatabase import PlantDataBase from plantdatabase import PlantDataBase
@@ -19,105 +20,249 @@ from robot import Robot
def data_sensordata(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase, def data_sensordata(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase,
robot: Robot): 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")) str_in = str(message.payload.decode("UTF-8"))
payload = json.loads(str_in) payload = json.loads(str_in)
logging.info("ROBOT_DATA_SENSORDATA Received data: " + json.dumps(payload)) logging.info("ROBOT_DATA_SENSORDATA Received data: " + json.dumps(payload))
drive_data = { drive_data = {
"PlantID": payload['PlantID'], "PlantID": [payload['PlantID']],
"ActionID": payload['ActionID'] "ActionID": payload['ActionID']
} }
try: try:
# Delete order from order list
robot.delete_order(drive_data) robot.delete_order(drive_data)
# Save data in database
mydatabase.insert_measurement_data(plant_id=payload['PlantID'], mydatabase.insert_measurement_data(plant_id=payload['PlantID'],
sensordata_temp=payload['AirTemperature'], sensordata_temp=payload['AirTemperature'],
sensordata_humidity=payload['AirHumidity'], sensordata_humidity=payload['AirHumidity'],
sensordata_soil_moisture=payload['SoilMoisture'], sensordata_soil_moisture=payload['SoilMoisture'],
sensordata_brightness=payload['Brightness']) sensordata_brightness=payload['Brightness'])
logging.debug("Inserted to data base: " + json.dumps(payload)) 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: except Exception as e:
logging.error("Could not delete order: " + str(e)) logging.error("Could not delete order: " + str(e))
def data_position(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, robot: Robot): 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"))) 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"]) robot.store_position(json.loads(message.payload.decode("UTF-8"))["Position"])
position_data = { position_data = {
"Position": robot.get_position(), "Position": robot.get_position(),
"Timestamp": str(datetime.now()) "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)) client.publish(Topics['BACKEND_DATA_POSITION'], json.dumps(position_data))
def data_battery(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, robot: Robot): 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")))) 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"]) robot.store_battery(json.loads(message.payload.decode("UTF-8"))["Battery"])
battery_data = { battery_data = {
"Battery": robot.get_battery(), "Battery": robot.get_battery(),
"Timestamp": str(datetime.now()) "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_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): 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")) 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()) 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")) client.publish(Topics['BACKEND_DATA_ERROR'], message.payload.decode("UTF-8"))
def data_robotready(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, robot: Robot): 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') 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: 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())) client.publish(Topics['ROBOT_ACTION_DRIVE'], json.dumps(robot.get_next_order()))
logging.info("Waiting Order send to Robot") logging.info("Waiting Order send to Robot")
logging.info("ROBOT_DATA_ROBOTREADY status updated: " + str(robot.get_robot_status())) 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 # FrontEnd Channel Reactions
def action_drive(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase, def action_drive(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase,
robot: Robot): 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()) action_id = str(uuid.uuid4())
drive_data = { drive_data = {
"PlantID": plant_id, "PlantID": plant_id,
"ActionID": action_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: 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)) client.publish(Topics['ROBOT_ACTION_DRIVE'], json.dumps(drive_data))
logging.info("BACKEND_ACTION_DRIVE Drive Command published: " + json.dumps(drive_data)) logging.info("BACKEND_ACTION_DRIVE Drive Command published: " + json.dumps(drive_data))
else: else:
if robot.get_order_number() < 6: if robot.get_order_number() < 6:
# Add to order list if robot not available
robot.add_order(drive_data) robot.add_order(drive_data)
logging.info("BACKEND_ACTION_DRIVE New data added to order list: " + str(drive_data)) logging.info("BACKEND_ACTION_DRIVE New data added to order list: " + str(drive_data))
elif robot.get_order_number() >= 6: elif robot.get_order_number() >= 6:
# Discard order if list full
logging.error("Could not add Order to list. Order discarded") logging.error("Could not add Order to list. Order discarded")
client.publish(Topics['BACKEND_DATA_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): 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_names = mydatabase.get_plant_names()
plant_ids = [] plant_ids = []
print(plant_names)
for names in plant_names: for names in plant_names:
_id = mydatabase.get_plant_id(names) _id = mydatabase.get_plant_id(names[0])
plant_ids.append(_id) plant_ids.append(_id)
# Create a new order number
action_id = str(uuid.uuid4()) action_id = str(uuid.uuid4())
drive_data = { drive_data = {
"PlantID": plant_ids, "PlantID": plant_ids,
"ActionID": action_id "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: if robot.get_order_number() < 6 and robot.get_robot_status() is True:
robot.add_order(drive_data) 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)) logging.info("BACKEND_ACTION_DRIVEALL Drive Command published: " + json.dumps(drive_data))
else: else:
if robot.get_order_number() < 6: 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): 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']) client.publish(Topics['ROBOT_ACTION_GETPOSITION'])
logging.info("BACKEND_ACTION_GETPOSITION message forwarded to Robot") logging.info("BACKEND_ACTION_GETPOSITION message forwarded to Robot")
def action_getbattery(client: mqtt.Client, userdata, message: mqtt.MQTTMessage): 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']) client.publish(Topics['ROBOT_ACTION_GETBATTERY'])
logging.info("BACKEND_ACTION_GETBATTERY message forwarded to Robot") 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): 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() plant_names = mydatabase.get_plant_names()
alldata = [] alldata = []
# Get the latest data from all registered plant names by using the plant names
for i in 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)) client.publish(Topics['BACKEND_DATA_SENSORDATAALL'], json.dumps(alldata))
logging.info("BACKEND_DATA_SENSORDATAALL got data from database:" + 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): 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")) 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"]) 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"], mydatabase.insert_measurement_data(plant_id=plant_data["PlantID"],
sensordata_temp=plant_data["AirTemperature"], sensordata_temp=plant_data["AirTemperature"],
sensordata_humidity=plant_data["AirHumidity"], sensordata_humidity=plant_data["AirHumidity"],
sensordata_soil_moisture=plant_data["SoilMoisture"], sensordata_soil_moisture=plant_data["SoilMoisture"],
sensordata_brightness=plant_data["Brightness"]) sensordata_brightness=plant_data["Brightness"])
logging.info("BACKEND_ACTION_NEWPLANT new plant data received and inserted: " + str(plant_data)) 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) action_getalldata(client, userdata, message, mydatabase)
def action_configureplant(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase): 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")) 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"]) 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"], mydatabase.insert_measurement_data(plant_id=plant_data["PlantID"],
sensordata_temp=plant_data["AirTemperature"], sensordata_temp=plant_data["AirTemperature"],
sensordata_humidity=plant_data["AirHumidity"], sensordata_humidity=plant_data["AirHumidity"],
sensordata_soil_moisture=plant_data["SoilMoisture"], sensordata_soil_moisture=plant_data["SoilMoisture"],
sensordata_brightness=plant_data["Brightness"]) sensordata_brightness=plant_data["Brightness"])
logging.info("BACKEND_ACTION_CONFIGUREPLANT configure plant data received and inserted: " + str(plant_data)) 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) action_getalldata(client, userdata, message, mydatabase)
def action_deleteplant(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase): 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_plant = json.loads(message.payload.decode("UTF-8"))
# Delete the plant
mydatabase.delete_plant(plant_id=delete_plant) mydatabase.delete_plant(plant_id=delete_plant)
logging.info("BACKEND_ACTION_DELETEPLANT delete plant data received and deleted: " + str(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) action_getalldata(client, userdata, message, mydatabase)
def action_countplants(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase): 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() count = mydatabase.plant_count()
# Create Object and send to the FrontEnd
count_payload = { count_payload = {
"CurrentCount": count, "CurrentCount": count,
"MaxCount": MAX_PLANT_COUNT "MaxCount": MAX_PLANT_COUNT
+3 -2
View File
@@ -1,10 +1,11 @@
""" """
created by caliskan at 19.04.2023 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" MQTT_BROKER_GLOBAL = "mqtt.eclipseprojects.io"
RASPI_CLIENT_ID = "smart_farming_raspi" RASPI_CLIENT_ID = "smart_farming_raspi"
BACKEND_CLIENT_ID = "smart_farming_server" 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)
+16 -4
View File
@@ -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. This method gets called, when it connects to a mqtt broker.
It is used to subscribe to the specific topics It is used to subscribe to the specific topics
:param _robot: :param _robot: robot object for
:param _mydatabase: :param _mydatabase: object, that contains the custom plant database and methods for its usage
:param _client: mqtt client object :param _client: mqtt client object
:param _userdata: :param _userdata:
:param _flags: :param _flags:
:param _rc: connection flag :param _rc: connection flag
:return: :return: none
""" """
if _rc == 0: if _rc == 0:
print("connected") 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. _client.message_callback_add(Topics['ROBOT_DATA_SENSORDATA'], lambda client, userdata, message: data_functions.
data_sensordata(client, userdata, message, _mydatabase, _robot)) 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.subscribe(Topics['ROBOT_DATA_POSITION'])
_client.message_callback_add(Topics['ROBOT_DATA_POSITION'], data_functions.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(): def main():
# Create Robot Object
robot = Robot() robot = Robot()
# Connect to Plant_Database and create tables if database did not exist
my_database = PlantDataBase(database_name=DATABASE_NAME) my_database = PlantDataBase(database_name=DATABASE_NAME)
my_database.create_tables() 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, mqttclient.on_connect = lambda client, userdata, flags, rc: on_connect(_client=client,
_userdata=userdata, _userdata=userdata,
_flags=flags, _flags=flags,
@@ -108,10 +116,14 @@ def main():
_mydatabase=my_database, _mydatabase=my_database,
_robot=robot) _robot=robot)
mqttclient.connect(MQTT_BROKER_GLOBAL) 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, logging.basicConfig(filename="server.log", filemode="a", encoding="utf-8", level=logging.DEBUG,
format='%(asctime)s %(name)s %(levelname)s %(message)s', format='%(asctime)s %(name)s %(levelname)s %(message)s',
datefmt="%d-%m-%Y %H:%M:%S") datefmt="%d-%m-%Y %H:%M:%S")
logging.getLogger().addHandler(logging.StreamHandler(sys.stdout)) logging.getLogger().addHandler(logging.StreamHandler(sys.stdout))
# Starting mqttclient infinite loop
mqttclient.loop_forever() mqttclient.loop_forever()
+70 -9
View File
@@ -2,30 +2,47 @@
import sqlite3 import sqlite3
from typing import Optional from typing import Optional
import logging import logging
from itertools import chain
class PlantDataBase: 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): 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 self.conn = None
try: try:
# connect or create new database if not available
self.conn = sqlite3.connect(self.db_file, check_same_thread=False) self.conn = sqlite3.connect(self.db_file, check_same_thread=False)
except sqlite3.Error as e: except sqlite3.Error as e:
logging.error("Database init error: " + str(e)) logging.error("Database init error: " + str(e))
# cursor on the database
self.cur = self.conn.cursor() self.cur = self.conn.cursor()
def create_tables(self): 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: try:
# Create plants table if not already existing
table_config = "CREATE TABLE IF NOT EXISTS plants " \ table_config = "CREATE TABLE IF NOT EXISTS plants " \
"(PlantID INTEGER PRIMARY KEY," \ "(PlantID INTEGER PRIMARY KEY," \
"PlantName TEXT)" "PlantName TEXT)"
self.cur.execute(table_config) self.cur.execute(table_config)
# Create measurement_values_table if not already existing
table_config = "CREATE TABLE IF NOT EXISTS measurement_values " \ table_config = "CREATE TABLE IF NOT EXISTS measurement_values " \
"(measurementID INTEGER PRIMARY KEY," \ "(measurementID INTEGER PRIMARY KEY," \
"Timestamp DATETIME DEFAULT (datetime('now', 'localtime'))," \ "Timestamp DATETIME DEFAULT (datetime('now', 'localtime'))," \
@@ -43,6 +60,12 @@ class PlantDataBase:
return False return False
def insert_plant(self, plantname: str, plant_id: int) -> bool: 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: try:
self.cur.execute("INSERT INTO plants (PlantName, PlantID) VALUES (?,?)", (plantname, plant_id)) self.cur.execute("INSERT INTO plants (PlantName, PlantID) VALUES (?,?)", (plantname, plant_id))
self.conn.commit() self.conn.commit()
@@ -52,6 +75,12 @@ class PlantDataBase:
return False return False
def configure_plant(self, plant_id: int, plantname: str) -> bool: 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: try:
self.cur.execute("UPDATE plants SET PlantID = ?, PlantName = ? WHERE PlantID= ?", self.cur.execute("UPDATE plants SET PlantID = ?, PlantName = ? WHERE PlantID= ?",
(plant_id, plantname, plant_id)) (plant_id, plantname, plant_id))
@@ -62,6 +91,11 @@ class PlantDataBase:
return False return False
def delete_plant(self, plant_id): 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: try:
self.cur.execute('DELETE FROM plants WHERE PlantID = ?', (plant_id,)) self.cur.execute('DELETE FROM plants WHERE PlantID = ?', (plant_id,))
self.conn.commit() self.conn.commit()
@@ -75,6 +109,15 @@ class PlantDataBase:
sensordata_humidity, sensordata_humidity,
sensordata_soil_moisture, sensordata_soil_moisture,
sensordata_brightness) -> bool: 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: try:
self.cur.execute(f"INSERT INTO measurement_values (PlantID, AirTemperature, AirHumidity," self.cur.execute(f"INSERT INTO measurement_values (PlantID, AirTemperature, AirHumidity,"
f"SoilMoisture, Brightness) VALUES " 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): 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 Gets the newest parameter of specific plant and returns all parameters in json format.
:param plant_id: Either pass plant_name OR plant_id, BOTH passed -> ERROR
:param plant_name: :param plant_id: PlantID of plant
:return: :param plant_name: Name of Plant
:return: JSON with data if successfully, else none
""" """
try: try:
if plant_name is not None and plant_id is None: 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)) logging.error("Could not get measurement values: " + str(e))
def delete_data(self, table_name): 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: try:
self.cur.execute(f'DELETE FROM {table_name}') self.cur.execute(f'DELETE FROM {table_name}')
self.conn.commit() self.conn.commit()
@@ -129,7 +178,7 @@ class PlantDataBase:
def plant_count(self) -> int: def plant_count(self) -> int:
""" """
returns the number of plants registered in the database returns the number of plants registered in the database
:return: :return: Count of registered plants as int
""" """
try: try:
self.cur.execute("SELECT COUNT(*) FROM plants") self.cur.execute("SELECT COUNT(*) FROM plants")
@@ -138,13 +187,22 @@ class PlantDataBase:
logging.error("Could not count plants: " + str(e)) logging.error("Could not count plants: " + str(e))
def get_plant_names(self) -> list: 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: try:
self.cur.execute("SELECT PlantName FROM plants") self.cur.execute("SELECT PlantName FROM plants")
return [row[0] for row in self.cur.fetchall()] return self.cur.fetchall()
except Exception as e: except Exception as e:
logging.error("Could not get plant names: " + str(e)) logging.error("Could not get plant names: " + str(e))
def get_plant_id(self, plant_name: str) -> int: 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: try:
self.cur.execute("SELECT PlantID FROM plants WHERE PlantName=?", (plant_name,)) self.cur.execute("SELECT PlantID FROM plants WHERE PlantName=?", (plant_name,))
return self.cur.fetchone()[0] return self.cur.fetchone()[0]
@@ -152,4 +210,7 @@ class PlantDataBase:
logging.error("Could not get plant id: " + str(e)) logging.error("Could not get plant id: " + str(e))
def __del__(self): def __del__(self):
"""
Destructor of Class. Disconnects from database.
"""
self.conn.close() self.conn.close()
+1 -1
View File
@@ -1,7 +1,7 @@
class Robot: class Robot:
""" """
This class contains the features of the robot. It is used as an interface for the main to avoid global variables and 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): def __init__(self):
self.robot_ready = True self.robot_ready = True
-2
View File
@@ -65,7 +65,6 @@ ALLSENSORDATA = {
"ActionID": "" "ActionID": ""
} }
# TODO When sensor is available
POSITION = { POSITION = {
"Position": "" "Position": ""
} }
@@ -115,7 +114,6 @@ ALLPLANTDATA = [
PLANTDATA PLANTDATA
] ]
# TODO When sensor is available
POSITION = { POSITION = {
"Position": "", "Position": "",
"Timestamp": "" "Timestamp": ""
+31 -21
View File
@@ -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 Reads all sensors and publishes data via MQTT in form of SENSORDATA
***Function is only neccessary for driving without color codes***
Args: Args:
plantID (_type_): plant to measure plantID (_type_): plant to measure
actionID (_type_): current ID of driving action 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 Meassure and publish data via MQTT
Drive home to starting point 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: Args:
plantID (_type_): plant to measure 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') 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 != 0:
if errorCode == 65280: if errorCode == 65280:
errorMessage = "EV3 not connected" errorMessage = "EV3 not connected"
else: else:
errorMessage = "Motor or Sensor unplugged" 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_ERROR"], f"{errorMessage}, Drive Plant aborted, Robot at starting position", qos=1)
client.publish(Topics["ROBOT_DATA_ROBOTREADY"], "True", qos=1) client.publish(Topics["ROBOT_DATA_ROBOTREADY"], "True", qos=1)
return return
logging.info("Measuring Sensors") logging.info("Measuring Sensors")
measure_send_data(plantID, actionID, client) measure_send_data(plantID, actionID, client)
# TODO How to Handle Pictures and PlantID # Taking Picture not fully implemented
logging.info("Taking Picture") # logging.info("Taking Picture")
sendPicture(client) # sendPicture(client)
logging.info("Robot driving home") logging.info("Robot driving home")
errorCode = os.system(f'sshpass -p maker ssh robot@ev3dev.local python3 /home/robot/Programme/drive_back.py {plantID}') 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 != 0:
if errorCode == 65280: if errorCode == 65280:
errorMessage = "EV3 not connected" errorMessage = "EV3 not connected"
else: else:
errorMessage = "Motor or Sensor unplugged" 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_ERROR"], f"{errorMessage}, Drive Plant aborted, Robot at plant {plantID}", qos=1)
client.publish(Topics["ROBOT_DATA_ROBOTREADY"], "True", qos=1) client.publish(Topics["ROBOT_DATA_ROBOTREADY"], "True", qos=1)
return return
@@ -111,7 +116,7 @@ def drive_plant_all_thread(plantIDs: list, actionID, client: mqtt.Client):
client (mqtt.Client): current MQTT client client (mqtt.Client): current MQTT client
""" """
allPlantData = { allPlantData = { # Sensordata for all plants
"SensorData": [], "SensorData": [],
"ActionID": "" "ActionID": ""
} }
@@ -119,17 +124,18 @@ def drive_plant_all_thread(plantIDs: list, actionID, client: mqtt.Client):
allPlantData["ActionID"] = actionID allPlantData["ActionID"] = actionID
for plant in plantIDs: for plant in plantIDs:
# FIXME Only possible with color codes
logging.info(f"Robot Driving to plant {plant}") 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}') 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 != 0:
if errorCode == 65280: if errorCode == 65280:
errorMessage = "EV3 not connected" errorMessage = "EV3 not connected"
else: else:
errorMessage = "Motor or Sensor unplugged" 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_ERROR"], f"{errorMessage}, Drive Plant aborted, Robot at starting position", qos=1)
client.publish(Topics["ROBOT_DATA_ROBOTREADY"], "True", qos=1) client.publish(Topics["ROBOT_DATA_ROBOTREADY"], "True", qos=1)
return return
@@ -149,27 +155,32 @@ def drive_plant_all_thread(plantIDs: list, actionID, client: mqtt.Client):
allPlantData["SensorData"].append(sensordata) allPlantData["SensorData"].append(sensordata)
# TODO How to Handle Pictures and PlantID # Taking Picture not fully implemented
logging.info("Taking Picture") # logging.info("Taking Picture")
sendPicture(client) # sendPicture(client)
errorCode = os.system(f'sshpass -p maker ssh robot@ev3dev.local python3 /home/robot/Programme/drive_arm.py {plant}') 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 != 0:
if errorCode == 65280: if errorCode == 65280:
errorMessage = "EV3 not connected" errorMessage = "EV3 not connected"
else: else:
errorMessage = "Motor or Sensor unplugged" 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_ERROR"], f"{errorMessage}, Drive Plant aborted, Robot at plant {plant}", qos=1)
client.publish(Topics["ROBOT_DATA_ROBOTREADY"], "True", qos=1) client.publish(Topics["ROBOT_DATA_ROBOTREADY"], "True", qos=1)
return return
# End of drive and measure operation
logging.info("All Plants measured, sending data") logging.info("All Plants measured, sending data")
client.publish(Topics["ROBOT_DATA_ALL"], json.dumps(allPlantData, indent=4), qos=1) client.publish(Topics["ROBOT_DATA_ALL"], json.dumps(allPlantData, indent=4), qos=1)
logging.info("Robot driving home") 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') errorCode = os.system(f'sshpass -p maker ssh robot@ev3dev.local python3 /home/robot/Programme/drive_back_straight.py')
if errorCode != 0: if errorCode != 0:
if errorCode == 65280: if errorCode == 65280:
@@ -177,7 +188,7 @@ def drive_plant_all_thread(plantIDs: list, actionID, client: mqtt.Client):
else: else:
errorMessage = "Motor or Sensor unplugged" 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_ERROR"], f"{errorMessage}, Drive Plant aborted, Robot at plant {plant}", qos=1)
client.publish(Topics["ROBOT_DATA_ROBOTREADY"], "True", qos=1) client.publish(Topics["ROBOT_DATA_ROBOTREADY"], "True", qos=1)
return return
@@ -196,7 +207,7 @@ def drive_plant(client: mqtt.Client, userdata, message: mqtt.MQTTMessage):
Function to drive to plant according to request Function to drive to plant according to request
Starting Drive in Thread to not block main programm 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: Args:
client (mqtt.Client): current mqtt client client (mqtt.Client): current mqtt client
@@ -215,7 +226,6 @@ def drive_plant(client: mqtt.Client, userdata, message: mqtt.MQTTMessage):
thread.start() thread.start()
# FIXME Only possible with color codes
def drive_plant_all(client: mqtt.Client, userdata, message: mqtt.MQTTMessage): def drive_plant_all(client: mqtt.Client, userdata, message: mqtt.MQTTMessage):
""" """
Function to drive to plants according to request 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() 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 Callback function for GPS position request
Function to send actual GPS position via MQTT in form of POSITION 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") logging.info("Received position request")
# TODO Write GPS Sensor Function
position = { position = {
"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): def get_batteryStatus(client: mqtt.Client, userdata, message: mqtt.MQTTMessage):
""" """
Callback function for battery status request Callback function for battery status request
Function to read battery status from EV3 and send via MQTT in form of BATTERY 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 Reading battery level via SSH script execution
Battery level shown in Volts: Battery level shown in Volts:
8,5V -> 100% 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()) 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: except:
logging.error("EV3 not connected") 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 return
batteryLevel = round(batteryLevel / 1000000, 2) # Conversion to Volt batteryLevel = round(batteryLevel / 1000000, 2) # Conversion to Volt
+1 -1
View File
@@ -45,7 +45,7 @@ def on_connect(client: mqtt.Client, userdata, flags, rc):
def main(): def main():
""" """
Initialises MQTT and Sensors Initialises MQTT
Runs forever and controlls all robot functions 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', logging.basicConfig(filename="robot.log", filemode="a", encoding="utf-8", level=logging.DEBUG, format='%(asctime)s %(name)s %(levelname)s %(message)s',
+8 -9
View File
@@ -42,7 +42,7 @@ def readDHT22():
def readTSL2561(): def readTSL2561():
""" """
Reads TSL2561 brightness in Lux and returns integer value Reads TSL2561 brightness via I2C in Lux and returns integer value
Raises: Raises:
Exception: If TSL2561 not connected properly Exception: If TSL2561 not connected properly
@@ -67,7 +67,7 @@ def readTSL2561():
def readMCP3008(): 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: Raises:
Exception: If YL-69 not connected properly Exception: If YL-69 not connected properly
@@ -99,6 +99,7 @@ def readSensors(sensorData):
""" """
Read DHT22, TSL2561 and Humidity Sensor Read DHT22, TSL2561 and Humidity Sensor
Dictionary is passed to ensure that values are available when errors occur 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: Args:
sensorData (dictionary): Dictionary of type SENSORDATA sensorData (dictionary): Dictionary of type SENSORDATA
@@ -114,7 +115,7 @@ def readSensors(sensorData):
try: try:
sensorData["AirTemperature"], sensorData["AirHumidity"] = readDHT22() sensorData["AirTemperature"], sensorData["AirHumidity"] = readDHT22()
except Exception as e: 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 sensorData["AirTemperature"] = 0
errorMessage = str(e) + "\n" # Appending received error message to later forward all occured errors errorMessage = str(e) + "\n" # Appending received error message to later forward all occured errors
@@ -122,17 +123,17 @@ def readSensors(sensorData):
try: try:
sensorData["Brightness"] = readTSL2561() sensorData["Brightness"] = readTSL2561()
except Exception as e: 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 errorMessage = errorMessage + str(e) + "\n" # Appending received error message to later forward all occured errors
# read YL-69 # read YL-69
try: try:
sensorData["SoilMoisture"] = readMCP3008() sensorData["SoilMoisture"] = readMCP3008()
except Exception as e: 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 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 != "": if errorMessage != "":
raise Exception(errorMessage) raise Exception(errorMessage)
@@ -146,15 +147,13 @@ def takePicture():
try: try:
camera = PiCamera() camera = PiCamera()
except: except:
# raise Exception("Camera not connected") raise Exception("Camera not connected")
pass
camera.start_preview() camera.start_preview()
camera.capture("picture.png") camera.capture("picture.png")
camera.stop_preview() camera.stop_preview()
# TODO - read position with sensor
def readPosition(): def readPosition():
""" """
Read and return Position Read and return Position