Browse Source

Erster und wichtigster Push :D

master
Kemal Duelger 1 year ago
parent
commit
9166d4040e
100 changed files with 24231 additions and 0 deletions
  1. 16
    0
      software/Frontend/.browserslistrc
  2. 16
    0
      software/Frontend/.editorconfig
  3. 42
    0
      software/Frontend/.gitignore
  4. 27
    0
      software/Frontend/README.md
  5. 105
    0
      software/Frontend/angular.json
  6. 44
    0
      software/Frontend/karma.conf.js
  7. 21168
    0
      software/Frontend/package-lock.json
  8. 50
    0
      software/Frontend/package.json
  9. 1
    0
      software/Frontend/projektarbeit_duelger_waldhauser_caliskan
  10. 55
    0
      software/Frontend/src/app/Home/home.component.css
  11. 16
    0
      software/Frontend/src/app/Home/home.component.html
  12. 23
    0
      software/Frontend/src/app/Home/home.component.spec.ts
  13. 16
    0
      software/Frontend/src/app/Home/home.component.ts
  14. 39
    0
      software/Frontend/src/app/Home/input/input.component.css
  15. 10
    0
      software/Frontend/src/app/Home/input/input.component.html
  16. 69
    0
      software/Frontend/src/app/Home/input/input.component.spec.ts
  17. 51
    0
      software/Frontend/src/app/Home/input/input.component.ts
  18. 34
    0
      software/Frontend/src/app/Home/output/output.component.css
  19. 8
    0
      software/Frontend/src/app/Home/output/output.component.html
  20. 23
    0
      software/Frontend/src/app/Home/output/output.component.spec.ts
  21. 38
    0
      software/Frontend/src/app/Home/output/output.component.ts
  22. 16
    0
      software/Frontend/src/app/Home/speech.service.spec.ts
  23. 75
    0
      software/Frontend/src/app/Home/speech.service.ts
  24. 5
    0
      software/Frontend/src/app/Map/map.component.css
  25. 1
    0
      software/Frontend/src/app/Map/map.component.html
  26. 23
    0
      software/Frontend/src/app/Map/map.component.spec.ts
  27. 23
    0
      software/Frontend/src/app/Map/map.component.ts
  28. 85
    0
      software/Frontend/src/app/Plants/plant-card/plant-card.component.css
  29. 38
    0
      software/Frontend/src/app/Plants/plant-card/plant-card.component.html
  30. 23
    0
      software/Frontend/src/app/Plants/plant-card/plant-card.component.spec.ts
  31. 62
    0
      software/Frontend/src/app/Plants/plant-card/plant-card.component.ts
  32. 39
    0
      software/Frontend/src/app/Plants/plants.component.css
  33. 32
    0
      software/Frontend/src/app/Plants/plants.component.html
  34. 23
    0
      software/Frontend/src/app/Plants/plants.component.spec.ts
  35. 56
    0
      software/Frontend/src/app/Plants/plants.component.ts
  36. 88
    0
      software/Frontend/src/app/Robot/robot.component.css
  37. 18
    0
      software/Frontend/src/app/Robot/robot.component.html
  38. 23
    0
      software/Frontend/src/app/Robot/robot.component.spec.ts
  39. 53
    0
      software/Frontend/src/app/Robot/robot.component.ts
  40. 16
    0
      software/Frontend/src/app/Service/Mqtt/mqtt-request.service.spec.ts
  41. 222
    0
      software/Frontend/src/app/Service/Mqtt/mqtt-request.service.ts
  42. 16
    0
      software/Frontend/src/app/Service/Mqtt/mqtt-set-data.service.spec.ts
  43. 36
    0
      software/Frontend/src/app/Service/Mqtt/mqtt-set-data.service.ts
  44. 16
    0
      software/Frontend/src/app/Service/Mqtt/mqtt.service.spec.ts
  45. 37
    0
      software/Frontend/src/app/Service/Mqtt/mqtt.service.ts
  46. 16
    0
      software/Frontend/src/app/Service/store.service.spec.ts
  47. 96
    0
      software/Frontend/src/app/Service/store.service.ts
  48. 10
    0
      software/Frontend/src/app/app-routing.module.ts
  49. 44
    0
      software/Frontend/src/app/app.component.css
  50. 36
    0
      software/Frontend/src/app/app.component.html
  51. 59
    0
      software/Frontend/src/app/app.component.spec.ts
  52. 27
    0
      software/Frontend/src/app/app.component.ts
  53. 87
    0
      software/Frontend/src/app/app.module.ts
  54. 78
    0
      software/Frontend/src/app/dialog/add-plant-dialog/add-plant-dialog.component.css
  55. 43
    0
      software/Frontend/src/app/dialog/add-plant-dialog/add-plant-dialog.component.html
  56. 172
    0
      software/Frontend/src/app/dialog/add-plant-dialog/add-plant-dialog.component.spec.ts
  57. 100
    0
      software/Frontend/src/app/dialog/add-plant-dialog/add-plant-dialog.component.ts
  58. 62
    0
      software/Frontend/src/app/dialog/configure-plant-dialog/configure-plant-dialog.component.css
  59. 34
    0
      software/Frontend/src/app/dialog/configure-plant-dialog/configure-plant-dialog.component.html
  60. 23
    0
      software/Frontend/src/app/dialog/configure-plant-dialog/configure-plant-dialog.component.spec.ts
  61. 60
    0
      software/Frontend/src/app/dialog/configure-plant-dialog/configure-plant-dialog.component.ts
  62. 10
    0
      software/Frontend/src/app/dialog/delete-confirm-dialog/delete-confirm-dialog.component.css
  63. 8
    0
      software/Frontend/src/app/dialog/delete-confirm-dialog/delete-confirm-dialog.component.html
  64. 23
    0
      software/Frontend/src/app/dialog/delete-confirm-dialog/delete-confirm-dialog.component.spec.ts
  65. 24
    0
      software/Frontend/src/app/dialog/delete-confirm-dialog/delete-confirm-dialog.component.ts
  66. 10
    0
      software/Frontend/src/app/dialog/error-dialog/error-dialog.component.css
  67. 7
    0
      software/Frontend/src/app/dialog/error-dialog/error-dialog.component.html
  68. 23
    0
      software/Frontend/src/app/dialog/error-dialog/error-dialog.component.spec.ts
  69. 25
    0
      software/Frontend/src/app/dialog/error-dialog/error-dialog.component.ts
  70. 27
    0
      software/Frontend/src/app/dialog/map-plant-dialog/map-plant-dialog.component.css
  71. 7
    0
      software/Frontend/src/app/dialog/map-plant-dialog/map-plant-dialog.component.html
  72. 23
    0
      software/Frontend/src/app/dialog/map-plant-dialog/map-plant-dialog.component.spec.ts
  73. 15
    0
      software/Frontend/src/app/dialog/map-plant-dialog/map-plant-dialog.component.ts
  74. 10
    0
      software/Frontend/src/app/models/plant.model.ts
  75. 4
    0
      software/Frontend/src/app/models/plantCount.model.ts
  76. 4
    0
      software/Frontend/src/app/models/position.model.ts
  77. 4
    0
      software/Frontend/src/app/models/robotBattery.model.ts
  78. 0
    0
      software/Frontend/src/assets/.gitkeep
  79. BIN
      software/Frontend/src/assets/images/Icons-Card/Timestamp_icon.png
  80. BIN
      software/Frontend/src/assets/images/Icons-Card/air_humidity_icon.png
  81. BIN
      software/Frontend/src/assets/images/Icons-Card/air_temperature_icon.png
  82. BIN
      software/Frontend/src/assets/images/Icons-Card/brightness_icon.png
  83. BIN
      software/Frontend/src/assets/images/Icons-Card/id_icon.png
  84. BIN
      software/Frontend/src/assets/images/Icons-Card/name_icon.png
  85. BIN
      software/Frontend/src/assets/images/Icons-Card/soil_moisture_icon.png
  86. BIN
      software/Frontend/src/assets/images/background.jpg
  87. BIN
      software/Frontend/src/assets/images/gruppe1.jpg
  88. BIN
      software/Frontend/src/assets/images/gruppe2.jpg
  89. BIN
      software/Frontend/src/assets/images/logo.png
  90. 35
    0
      software/Frontend/src/custom-theme.scss
  91. 3
    0
      software/Frontend/src/environments/environment.prod.ts
  92. 16
    0
      software/Frontend/src/environments/environment.ts
  93. BIN
      software/Frontend/src/favicon.ico
  94. 19
    0
      software/Frontend/src/index.html
  95. 12
    0
      software/Frontend/src/main.ts
  96. 53
    0
      software/Frontend/src/polyfills.ts
  97. 4
    0
      software/Frontend/src/styles.css
  98. 26
    0
      software/Frontend/src/test.ts
  99. 15
    0
      software/Frontend/tsconfig.app.json
  100. 0
    0
      software/Frontend/tsconfig.json

+ 16
- 0
software/Frontend/.browserslistrc View File

@@ -0,0 +1,16 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries

# For the full list of supported browsers by the Angular framework, please see:
# https://angular.io/guide/browser-support

# You can see what browsers were selected by your queries by running:
# npx browserslist

last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions
Firefox ESR

+ 16
- 0
software/Frontend/.editorconfig View File

@@ -0,0 +1,16 @@
# Editor configuration, see https://editorconfig.org
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true

[*.ts]
quote_type = single

[*.md]
max_line_length = off
trim_trailing_whitespace = false

+ 42
- 0
software/Frontend/.gitignore View File

@@ -0,0 +1,42 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.

# Compiled output
/dist
/tmp
/out-tsc
/bazel-out

# Node
/node_modules
npm-debug.log
yarn-error.log

# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace

# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*

# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings

# System files
.DS_Store
Thumbs.db

+ 27
- 0
software/Frontend/README.md View File

@@ -0,0 +1,27 @@
# Frontend

This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.2.8.

## Development server

Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.

## Code scaffolding

Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.

## Build

Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.

## Running unit tests

Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).

## Running end-to-end tests

Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.

## Further help

To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.

+ 105
- 0
software/Frontend/angular.json View File

@@ -0,0 +1,105 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"Frontend": {
"projectType": "application",
"schematics": {},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/frontend",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/custom-theme.scss",
"src/styles.css",
"node_modules/leaflet/dist/leaflet.css"
],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"outputHashing": "all"
},
"development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "Frontend:build:production"
},
"development": {
"browserTarget": "Frontend:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "Frontend:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/custom-theme.scss",
"src/styles.css",
"node_modules/leaflet/dist/leaflet.css"
],
"scripts": []
}
}
}
}
}
}

+ 44
- 0
software/Frontend/karma.conf.js View File

@@ -0,0 +1,44 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html

module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
jasmine: {
// you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321`
},
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
jasmineHtmlReporter: {
suppressAll: true // removes the duplicated traces
},
coverageReporter: {
dir: require('path').join(__dirname, './coverage/frontend'),
subdir: '.',
reporters: [
{ type: 'html' },
{ type: 'text-summary' }
]
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};

+ 21168
- 0
software/Frontend/package-lock.json
File diff suppressed because it is too large
View File


+ 50
- 0
software/Frontend/package.json View File

@@ -0,0 +1,50 @@
{
"name": "frontend",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"private": true,
"dependencies": {
"@angular/animations": "^14.2.0",
"@angular/cdk": "^13.0.0",
"@angular/common": "^14.2.0",
"@angular/compiler": "^14.2.0",
"@angular/core": "^14.2.0",
"@angular/forms": "^14.2.0",
"@angular/material": "^13.0.0",
"@angular/platform-browser": "^14.2.0",
"@angular/platform-browser-dynamic": "^14.2.0",
"@angular/router": "^14.2.0",
"@types/googlemaps": "^3.43.3",
"@types/leaflet": "^1.9.3",
"leaflet": "^1.9.3",
"mqtt": "^4.3.7",
"ngx-mqtt": "^15.0.0-alpha.1",
"rxjs": "~7.5.0",
"tslib": "^2.3.0",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "^14.2.8",
"@angular/cli": "~14.2.8",
"@angular/compiler-cli": "^14.2.0",
"@types/jasmine": "~4.0.0",
"jasmine-core": "~4.3.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.0.0",
"typescript": "~4.7.2"
},
"description": "This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.2.8.",
"main": "karma.conf.js",
"keywords": [],
"author": "",
"license": "ISC"
}

+ 1
- 0
software/Frontend/projektarbeit_duelger_waldhauser_caliskan

@@ -0,0 +1 @@
Subproject commit a13b150dfe70b0beb1484f051ad4d7076ba7fd62

+ 55
- 0
software/Frontend/src/app/Home/home.component.css View File

@@ -0,0 +1,55 @@
#robotIsOnTour {
color: red;
font-size: larger;
}

#robotIsReady {
color: green;
font-size: larger;
}

.container {
display: grid;
grid-template-rows: 40% 20% 7%;
width: 100%;
height: 100%;
font-family: Cormorant;
}

.item-input {
grid-row: 1 / 2;
grid-column: 1/-1;
justify-self: center;
align-self: center;
}

.item-box-input {
width: 100%;
}

.item-box-output {
width: 100%;
}

.item-output {
grid-row: 3 / 4;
grid-column: 1/-1;
justify-self: start;
align-self: start;

}


.item-robotText {
grid-row: 2 / 3;
grid-column: 1/-1;
justify-self: center;
align-self: center;
font-size: 1.2rem;
padding: 0.7rem;
border: 0.125rem;
border-style: solid;
border-radius: 1rem;
border-color: silver;
box-shadow: 0.2rem 0.2rem 0.2rem silver;
}

+ 16
- 0
software/Frontend/src/app/Home/home.component.html View File

@@ -0,0 +1,16 @@
<div class="container">
<div class="item-box-input">
<div class="item-input">
<app-input></app-input>
</div>
</div>
<div class="item-box-output">
<div class="item-output">
<app-output></app-output>
</div>
</div>
<div class="item-robotText">
<div *ngIf="robotStateChange$ | async" id="robotIsOnTour">Robot is on Tour</div>
<div *ngIf="!(robotStateChange$ | async)" id="robotIsReady">Robot is Ready</div>
</div>
</div>

+ 23
- 0
software/Frontend/src/app/Home/home.component.spec.ts View File

@@ -0,0 +1,23 @@
// import { ComponentFixture, TestBed } from '@angular/core/testing';

// import { HomeComponent } from './home.component';

// describe('HomeComponent', () => {
// let component: HomeComponent;
// let fixture: ComponentFixture<HomeComponent>;

// beforeEach(async () => {
// await TestBed.configureTestingModule({
// declarations: [ HomeComponent ]
// })
// .compileComponents();

// fixture = TestBed.createComponent(HomeComponent);
// component = fixture.componentInstance;
// fixture.detectChanges();
// });

// it('should create', () => {
// expect(component).toBeTruthy();
// });
// });

+ 16
- 0
software/Frontend/src/app/Home/home.component.ts View File

@@ -0,0 +1,16 @@
import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { StoreService } from '../Service/store.service';

@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent {
robotStateChange$: Observable<boolean> = this.storeService.currentRobotReady;

constructor(private storeService: StoreService) {
}

}

+ 39
- 0
software/Frontend/src/app/Home/input/input.component.css View File

@@ -0,0 +1,39 @@
.container {
display: grid;
width: 100%;
height: 100%;
grid-template-rows: 25% 75%;
font-family: Cormorant;
margin-top: 3rem;
}

.inputText {
grid-row: 1 / 2;
justify-self: center;
align-self: center;
font-size: 2rem;
}

.inputMicro {
width: 8.3rem;
height: 100%;
grid-row: 2 / 3;
justify-self: center;
align-self: start;
}

button {
padding: 6.25rem 2.31rem;
border: 0.125rem;
border-style: solid;
border-radius: 10.625rem;
border-color: silver;
box-shadow: 0.2rem 0.2rem 0.2rem silver;
background-color: white;

}

mat-icon {
transform: scale(8);
margin-left: 1rem;
}

+ 10
- 0
software/Frontend/src/app/Home/input/input.component.html View File

@@ -0,0 +1,10 @@
<div class="container">
<div class="inputText">
<span>{{text}}</span>
</div>
<div class="inputMicro">
<button mat-menu-item (click)="startStopService()">
<mat-icon [ngStyle]="{'color': micIsClicked ? 'blue' : 'silver' }">micro</mat-icon>
</button>
</div>
</div>

+ 69
- 0
software/Frontend/src/app/Home/input/input.component.spec.ts View File

@@ -0,0 +1,69 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MqttRequestService } from 'src/app/Service/Mqtt/mqtt-request.service';
import { SpeechService } from '../speech.service';
import { InputComponent } from './input.component';

describe('InputComponent', () => {
let component: InputComponent;
let fixture: ComponentFixture<InputComponent>;
let speechService: SpeechService;
let mqttRequestService: MqttRequestService;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [InputComponent],
providers: [SpeechService, MqttRequestService],
}).compileComponents();
});

beforeEach(() => {
fixture = TestBed.createComponent(InputComponent);
component = fixture.componentInstance;
speechService = TestBed.inject(SpeechService);
mqttRequestService = TestBed.inject(MqttRequestService);
fixture.detectChanges();
});

it('should create the InputComponent', () => {
expect(component).toBeTruthy();
});

it('should initialize the SpeechService if not already initialized', () => {
spyOn(speechService, 'init');
component.ngOnInit();
expect(speechService.init).toHaveBeenCalled();
});

it('should update the text and publish to INPUT when receiving a message', () => {
const message = 'Test message';
spyOn(mqttRequestService, 'publishToINPUT');
component.ngOnInit();
// component.service.currentApprovalStageMessage.next(message);
expect(component.text).toBe(message);
expect(mqttRequestService.publishToINPUT).toHaveBeenCalledWith(message);
});

it('should stop the service when micIsClicked is true', () => {
spyOn(component.service, 'stop');
component.micIsClicked = true;
component.startStopService();
expect(component.service.stop).toHaveBeenCalled();
});

it('should start the service when micIsClicked is false', () => {
spyOn(component.service, 'start');
component.micIsClicked = false;
component.startStopService();
expect(component.service.start).toHaveBeenCalled();
});

it('should unsubscribe from subscription and call onDestroy', () => {
spyOn(component.subscription, 'unsubscribe');
spyOn(component.service, 'onDestory');
component.ngOnDestroy();
expect(component.subscription.unsubscribe).toHaveBeenCalled();
expect(component.service.onDestory).toHaveBeenCalled();
expect(component.text).toBe('');
});
});


+ 51
- 0
software/Frontend/src/app/Home/input/input.component.ts View File

@@ -0,0 +1,51 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs-compat';
import { MqttRequestService } from 'src/app/Service/Mqtt/mqtt-request.service';
import { SpeechService } from '../speech.service';


@Component({
selector: 'app-input',
templateUrl: './input.component.html',
styleUrls: ['./input.component.css']
})
export class InputComponent implements OnInit, OnDestroy {
text: string = 'Init';
micIsClicked = false;
subscription: Subscription;

constructor(
public service: SpeechService,
private mqttRequestService: MqttRequestService
) {
if (!this.service.initSpeechServiceStarted) {
this.service.init();
}
}

ngOnInit(): void {
this.subscription = this.service.currentApprovalStageMessage.subscribe(msg => {
this.text = msg;
if (this.text !== '') {
this.micIsClicked = false;
this.mqttRequestService.publishToINPUT(this.text);
}
});
}

startStopService() {
if (this.micIsClicked) {
this.micIsClicked = false;
this.service.stop();
} else {
this.micIsClicked = true;
this.service.start();
}
}
ngOnDestroy() {
this.subscription.unsubscribe()
this.service.onDestory();
this.text = '';
}

}

+ 34
- 0
software/Frontend/src/app/Home/output/output.component.css View File

@@ -0,0 +1,34 @@
.container {
display: grid;
grid-template-columns: 10% 90%;
height: 100%;
width: 100%;
font-family: Cormorant;

border: 0.125rem;
border-style: solid;
border-radius: 1rem;
border-color: silver;
box-shadow: 0.2rem 0.2rem 0.2rem silver;
}

.outputIcon {
grid-column: 1 / 2;
justify-self: start;
align-self: end;

}

.outputText {
grid-column: 2 / 3;
justify-self: start;
align-self: center;
font-size: 2rem;
margin: 0;
}

.mat-icon {
margin-left: 0.5rem;
margin-top: 0.5rem;
transform: scale(1.5);
}

+ 8
- 0
software/Frontend/src/app/Home/output/output.component.html View File

@@ -0,0 +1,8 @@
<div class="container">
<div class="outputIcon">
<mat-icon>volume_up</mat-icon>
</div>
<div class="outputText">
<div>{{outputText}}</div>
</div>
</div>

+ 23
- 0
software/Frontend/src/app/Home/output/output.component.spec.ts View File

@@ -0,0 +1,23 @@
// import { ComponentFixture, TestBed } from '@angular/core/testing';

// import { OutputComponent } from './output.component';

// describe('OutputComponent', () => {
// let component: OutputComponent;
// let fixture: ComponentFixture<OutputComponent>;

// beforeEach(async () => {
// await TestBed.configureTestingModule({
// declarations: [ OutputComponent ]
// })
// .compileComponents();

// fixture = TestBed.createComponent(OutputComponent);
// component = fixture.componentInstance;
// fixture.detectChanges();
// });

// it('should create', () => {
// expect(component).toBeTruthy();
// });
// });

+ 38
- 0
software/Frontend/src/app/Home/output/output.component.ts View File

@@ -0,0 +1,38 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';

import { MqttRequestService } from 'src/app/Service/Mqtt/mqtt-request.service';

declare var speechSynthesis: any;

@Component({
selector: 'app-output',
templateUrl: './output.component.html',
styleUrls: ['./output.component.css']
})
export class OutputComponent implements OnInit, OnDestroy {
subscription: Subscription;
outputText: string = '';
constructor(private mqttRequestService: MqttRequestService) { }

ngOnInit(): void {
this.subscription = this.mqttRequestService.responseMessage.subscribe(msg => {
setTimeout(() => {
this.outputText = msg;
this.speak(this.outputText);
}, 3000);
});
}

speak(text: string) {
const utterance = new SpeechSynthesisUtterance(text);
utterance.lang = 'en-US';
speechSynthesis.speak(utterance);
}

ngOnDestroy() {
this.subscription.unsubscribe();
this.mqttRequestService.onDestroy();
}

}

+ 16
- 0
software/Frontend/src/app/Home/speech.service.spec.ts View File

@@ -0,0 +1,16 @@
// import { TestBed } from '@angular/core/testing';

// import { SpeechService } from './speech.service';

// describe('SpeechService', () => {
// let service: SpeechService;

// beforeEach(() => {
// TestBed.configureTestingModule({});
// service = TestBed.inject(SpeechService);
// });

// it('should be created', () => {
// expect(service).toBeTruthy();
// });
// });

+ 75
- 0
software/Frontend/src/app/Home/speech.service.ts View File

@@ -0,0 +1,75 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs-compat';
declare var webkitSpeechRecognition: any;


@Injectable({
providedIn: 'root'
})
export class SpeechService {
private approvalStageMessage = new BehaviorSubject('');
currentApprovalStageMessage = this.approvalStageMessage.asObservable();
initSpeechServiceStarted: boolean = false;

recognition = new webkitSpeechRecognition();

isStoppedSpeechRecog = false;
public text = '';
tempWords: string | undefined;

constructor() {
this.recognition.pitch = 10;
this.recognition.rate = 3;
}

init() {
this.initSpeechServiceStarted = true;
this.recognition.interimResults = true;
this.recognition.lang = 'en-US';

this.recognition.addEventListener('result', (e: { results: Iterable<unknown> | ArrayLike<unknown>; }) => {
const transcript = Array.from(e.results)
.map((result: any) => result[0])
.map((result) => result.transcript)
.join('');
this.tempWords = transcript;
console.log('init:' + transcript);
});
}

start() {
this.text = '';
this.isStoppedSpeechRecog = false;
this.recognition.start();
console.log("Speech recognition started")
this.recognition.addEventListener('end', () => {
this.wordConcat()
this.recognition.stop();
if (!this.isStoppedSpeechRecog) {
this.isStoppedSpeechRecog = true;
console.log("End speech recognition");
this.approvalStageMessage.next(this.text);
}
});
}

stop() {
this.recognition.stop();
if (!this.isStoppedSpeechRecog) {
this.isStoppedSpeechRecog = true;
console.log("End speech recognition");
this.approvalStageMessage.next(this.text);
}
}

wordConcat() {
this.text = this.text + ' ' + this.tempWords;
this.tempWords = '';
}

onDestory() {
this.text = '';
this.approvalStageMessage.next(this.text);
this.isStoppedSpeechRecog = false;
}
}

+ 5
- 0
software/Frontend/src/app/Map/map.component.css View File

@@ -0,0 +1,5 @@
div {
height: 70%;
width: 80%;
margin: 2% 9%;
}

+ 1
- 0
software/Frontend/src/app/Map/map.component.html View File

@@ -0,0 +1 @@
<div #mapContainer></div>

+ 23
- 0
software/Frontend/src/app/Map/map.component.spec.ts View File

@@ -0,0 +1,23 @@
// import { ComponentFixture, TestBed } from '@angular/core/testing';

// import { MapComponent } from './map.component';

// describe('MapComponent', () => {
// let component: MapComponent;
// let fixture: ComponentFixture<MapComponent>;

// beforeEach(async () => {
// await TestBed.configureTestingModule({
// declarations: [MapComponent]
// })
// .compileComponents();

// fixture = TestBed.createComponent(MapComponent);
// component = fixture.componentInstance;
// fixture.detectChanges();
// });

// it('should create', () => {
// expect(component).toBeTruthy();
// });
// });

+ 23
- 0
software/Frontend/src/app/Map/map.component.ts View File

@@ -0,0 +1,23 @@
import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core';
import * as L from 'leaflet';
import 'leaflet/dist/leaflet.css';

@Component({
selector: 'app-map',
templateUrl: './map.component.html',
styleUrls: ['./map.component.css']
})
export class MapComponent implements AfterViewInit {
@ViewChild('mapContainer', { static: false }) mapContainer: ElementRef;

ngAfterViewInit(): void {
const map = L.map(this.mapContainer.nativeElement).setView([51.505, -0.09], 13);

L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
}).addTo(map);

L.marker([49.452785, 11.096963]).addTo(map) //Koordinaten [X,Y]
.bindPopup('Robot')
.openPopup();
}
}

+ 85
- 0
software/Frontend/src/app/Plants/plant-card/plant-card.component.css View File

@@ -0,0 +1,85 @@
.IMG-Plants {
border-radius: 0.03125rem;
width: 9.25rem;
height: 7.375rem;
margin: 0rem 0.3125rem;
}

.icons-measurements {
width: 1rem;
height: 0.75rem;
margin: 0rem 0.3125rem;
}

.container {
min-width: 40rem;
width: 100%;
min-height: 9rem;
padding: 0.3125rem;
box-shadow: 0.3125rem 0.3125rem 0.3125rem silver;
border-radius: 1.25rem;
border-color: silver;
border-width: 0.0625rem;
border-style: solid;
margin-bottom: 1rem;
font-family: Cormorant;
display: grid;
grid-template-columns: 45% 50% 5%;
justify-content: center;
align-content: center;
font-size: 1.125rem;
}

.item-header-content {
grid-column: 1/2;
justify-self: center;
align-self: center;
display: grid;
width: 100%;
grid-template-columns: 55% 45%;
grid-template-rows: 25% 75%;
justify-content: start;
align-content: start;
}

.item-header {
grid-column: 1/3;
grid-row: 1/2;
justify-self: center;
align-self: start;
font-size: 1.875rem;
margin-bottom: 1rem;
}

.item-Measurements-IconsAndNames {
grid-column: 1/2;
grid-row: 2/3;
justify-self: start;
align-self: center;
}

.item-Measurements-Values {
grid-column: 2/3;
justify-self: start;
align-self: center;
}

.item-IMG {
justify-self: center;
align-self: end;
}

.item-delete {
justify-self: end;
align-self: center;
}


.delete-icon {
transform: scale(1.2);
}


p {
margin: 0;
}

+ 38
- 0
software/Frontend/src/app/Plants/plant-card/plant-card.component.html View File

@@ -0,0 +1,38 @@
<div class="container">
<div class="item-header-content">
<div class="item-header">
<p class="card-title">{{plant.PlantName}}</p>
</div>
<div class="item-Measurements-IconsAndNames">
<img class="icons-measurements" src="assets/images/Icons-Card/air_humidity_icon.png" alt="not available">
Air Humidity: <br>
<img class="icons-measurements" src="assets/images/Icons-Card/air_temperature_icon.png" alt="not available">
Air Temperature: <br>
<img class="icons-measurements" src="assets/images/Icons-Card/soil_moisture_icon.png" alt="not available">
Soil Moisture: <br>
<img class="icons-measurements" src="assets/images/Icons-Card/brightness_icon.png" alt="not available">
Brightness:<br>
<img class="icons-measurements" src="assets/images/Icons-Card/Timestamp_icon.png" alt="not available">
Timestamp:
</div>
<div class="item-Measurements-Values">
<p>{{plant.AirHumidity}} %</p>
<p>{{plant.AirTemperature}} °C</p>
<p>{{plant.SoilMoisture}} %</p>
<p>{{plant.Brightness}} Lux</p>
<p>{{getFormattedLastRequestedTimestamp() | async}}</p>
</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">
</div>
<div class="item-delete">
<button class="item-Configure" mat-icon-button color="primary" (click)="onConfigurePlant()">
<mat-icon>create</mat-icon>
</button>
<button mat-icon-button color="warn" (click)="onDeletePlant()">
<mat-icon class="delete-icon">delete</mat-icon>
</button>
</div>
</div>

+ 23
- 0
software/Frontend/src/app/Plants/plant-card/plant-card.component.spec.ts View File

@@ -0,0 +1,23 @@
// import { ComponentFixture, TestBed } from '@angular/core/testing';

// import { PlantCardComponent } from './plant-card.component';

// describe('PlantCardComponent', () => {
// let component: PlantCardComponent;
// let fixture: ComponentFixture<PlantCardComponent>;

// beforeEach(async () => {
// await TestBed.configureTestingModule({
// declarations: [PlantCardComponent]
// })
// .compileComponents();

// fixture = TestBed.createComponent(PlantCardComponent);
// component = fixture.componentInstance;
// fixture.detectChanges();
// });

// it('should create', () => {
// expect(component).toBeTruthy();
// });
// });

+ 62
- 0
software/Frontend/src/app/Plants/plant-card/plant-card.component.ts View File

@@ -0,0 +1,62 @@
import { Component, Input } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { Observable, of } from 'rxjs';
import { MqttRequestService } from 'src/app/Service/Mqtt/mqtt-request.service';
import { ConfigurePlantDialogComponent } from 'src/app/dialog/configure-plant-dialog/configure-plant-dialog.component';
import { DeleteConfirmDialogComponent } from 'src/app/dialog/delete-confirm-dialog/delete-confirm-dialog.component';
import { Plant } from 'src/app/models/plant.model';

@Component({
selector: 'app-plant-card',
templateUrl: './plant-card.component.html',
styleUrls: ['./plant-card.component.css']
})
export class PlantCardComponent {
@Input() plant: Plant;
currentDate: Date;

constructor(public dialog: MatDialog, private mqttRequestService: MqttRequestService) {
}

getFormattedLastRequestedTimestamp(): Observable<string> {
return of(this.calculateTimestamp(this.plant.Timestamp))
}

calculateTimestamp(timestamp: string): string {
const datePlant = new Date(timestamp);
this.currentDate = new Date();
let seconds = ((this.currentDate.getTime() - datePlant.getTime()) / (1000));

const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);

if (days >= 1) {
return `${days} day${days > 1 ? 's' : ''} ago`;
} else if (hours >= 1) {
return `${hours} hour${hours > 1 ? 's' : ''} ago`;
} else if (minutes >= 1) {
return `${minutes} minute${minutes > 1 ? 's' : ''} ago`;
} else {
seconds = Math.floor(seconds);
return `${seconds} second${seconds !== 1 ? 's' : ''} ago`;
}
}

onDeletePlant() {
this.dialog.open(DeleteConfirmDialogComponent, {
data: this.plant,
width: '400px',
});
}

onConfigurePlant() {
this.dialog.open(ConfigurePlantDialogComponent, {
data: {
plant: this.plant
},
width: '400px',
});
}

}

+ 39
- 0
software/Frontend/src/app/Plants/plants.component.css View File

@@ -0,0 +1,39 @@
.container {
display: grid;
width: 100%;
grid-template-columns: 50% 50%;
grid-template-rows: 20% 80%;
margin-top: 3.125rem;
}

.buttonSearch {
background-color: white;
border: none;
}

.item-Search {
grid-row: 1 / 2;
grid-column: 1/2;
justify-self: end;
align-self: center;
margin-right: 8rem;
}

.item-NewPlant {
grid-row: 1 / 2;
grid-column: 2/3;
justify-self: start;
align-self: center;
margin-left: 18rem;
}

.item-Cards {
grid-row: 2 / -1;
grid-column: 1/3;
justify-self: center;
align-self: center;
}

mat-spinner {
margin: 7rem auto;
}

+ 32
- 0
software/Frontend/src/app/Plants/plants.component.html View File

@@ -0,0 +1,32 @@
<div class="container" *ngIf="(plants$ |async) !== undefined; else errorLoadingData">
<div class="item-Search">
<form class="example-form">
<mat-form-field class=" example-full-width" appearance="fill">
<input type="text" placeholder="Plant Name" aria-label="Plant Name" matInput [formControl]="myControl"
[matAutocomplete]="auto">
<mat-autocomplete autoActiveFirstOption #auto="matAutocomplete"
(optionSelected)='onOptionSelect($event.option.value)'>
<mat-option *ngFor="let option of filteredOptions | async" [value]="option">
{{option}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
<button class="buttonSearch"> <mat-icon matSuffix>search</mat-icon></button>
</form>
</div>
<div class="example-button-row item-NewPlant">
<button class="buttonNew" mat-raised-button color="primary" (click)="onNewPlant()">New Plant</button>
</div>
<div class="item-Cards">
<div *ngIf="selectedPlant === 'undefined'; else elseBlock">
<app-plant-card *ngFor="let plant of (plants$ | async)" [plant]="plant"></app-plant-card>
</div>
<ng-template #elseBlock>
<app-plant-card [plant]="onSearchPlant(selectedPlant) | async"></app-plant-card>
<button mat-raised-button color="primary" (click)="onOptionDefault()">Show all
Plants</button>
</ng-template>
</div>
</div>
<ng-template #errorLoadingData><mat-spinner></mat-spinner>
</ng-template>

+ 23
- 0
software/Frontend/src/app/Plants/plants.component.spec.ts View File

@@ -0,0 +1,23 @@
// import { ComponentFixture, TestBed } from '@angular/core/testing';

// import { PlantsComponent } from './plants.component';

// describe('PlantsComponent', () => {
// let component: PlantsComponent;
// let fixture: ComponentFixture<PlantsComponent>;

// beforeEach(async () => {
// await TestBed.configureTestingModule({
// declarations: [ PlantsComponent ]
// })
// .compileComponents();

// fixture = TestBed.createComponent(PlantsComponent);
// component = fixture.componentInstance;
// fixture.detectChanges();
// });

// it('should create', () => {
// expect(component).toBeTruthy();
// });
// });

+ 56
- 0
software/Frontend/src/app/Plants/plants.component.ts View File

@@ -0,0 +1,56 @@
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { Observable, map, startWith } from 'rxjs';
import { MqttRequestService } from '../Service/Mqtt/mqtt-request.service';
import { StoreService } from '../Service/store.service';
import { AddPlantDialogComponent } from '../dialog/add-plant-dialog/add-plant-dialog.component';
import { Plant } from '../models/plant.model';

@Component({
selector: 'app-plants',
templateUrl: './plants.component.html',
styleUrls: ['./plants.component.css']
})
export class PlantsComponent {
myControl = new FormControl('');
options$: Observable<string[]> = this.storeService.getAllPlantNames();
selectedPlant: string = 'undefined';
filteredOptions: Observable<string[]>;
plants$: Observable<Plant[]> = this.storeService.currentPlants;


constructor(public dialog: MatDialog, public storeService: StoreService, private mqttRequestService: MqttRequestService) {
this.options$.subscribe(response => {
this.filteredOptions = this.myControl.valueChanges.pipe(
startWith(''),
map(value => this._filter(value || '', response)),
);

})
}

onSearchPlant(plantName: string): Observable<Plant> {
return this.storeService.getPlant(plantName);
}

onNewPlant() {
this.dialog.open(AddPlantDialogComponent, {
width: '420px',
});
}

onOptionSelect(value: string) {
this.selectedPlant = value;
}

onOptionDefault() {
this.selectedPlant = 'undefined';
}

private _filter(value: string, options: string[]): string[] {
const filterValue = value.toLowerCase();

return options?.filter(option => option.toLowerCase().includes(filterValue));
}
}

+ 88
- 0
software/Frontend/src/app/Robot/robot.component.css View File

@@ -0,0 +1,88 @@
img {
border-radius: 0.5rem;
width: 15.625rem;
height: 12.5rem;
margin: 0rem 0.3125rem;
}

.container {
text-align: center;
display: grid;
grid-template-columns: 40.625rem 12.5rem;
grid-template-rows: 12.5rem 6.25rem 6.25rem;
width: 53.125rem;
min-width: 46.875rem;
font-family: Cormorant;
box-shadow: 0.3125rem 0.3125rem 0.3125rem silver;
border-radius: 1.25rem;
border-color: silver;
border-width: 0.0625rem;
border-style: solid;
padding: 0.625rem;
margin: auto;
margin-top: 3rem;
}

.robotImage {
grid-row: 1 / -1;
grid-column: 2 / 3;
justify-self: end;
align-self: start;
}

.robotAkku {
grid-row: 1 / 2;
grid-column: 1/2;
justify-self: start;
align-self: center;
display: grid;
grid-template-columns: 15.625rem 12.5rem 12.5rem;
align-items: center;
}

.akkuMsg {
grid-column: 1/2;
justify-self: start;
align-self: center;
margin: 0;
}

.akkuProgressbar {
grid-column: 2/3;
justify-self: center;
align-self: center;
}

.akkuValue {
grid-column: 3/4;
justify-self: start;
align-self: center;
margin: 0;
margin-left: 0.3125rem;
}

.robotCoordinates {
grid-row: 2 / 3;
grid-column: 1/2;
justify-self: start;
align-self: center;
display: grid;
grid-template-columns: 15.625rem 12.5rem;
align-items: center;
}

.item-timestamp-name {
grid-column: 1/2;
justify-self: start;
align-self: start;
}

.item-timestamp-value {
grid-column: 2/3;
justify-self: start;
align-self: center;
}

mat-spinner {
margin: 7rem auto;
}

+ 18
- 0
software/Frontend/src/app/Robot/robot.component.html View File

@@ -0,0 +1,18 @@
<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">
</div>
<div class="robotAkku">
<h1 class="akkuMsg">Robot Akku:</h1>
<mat-progress-bar class="akkuProgressbar" mode="determinate" [value]="batteryChargingStatus$ | async"
[color]="(getLoadingBarColor() | async)"></mat-progress-bar>
<h3 class="akkuValue">{{batteryChargingStatus$ | async}}%</h3>
</div>
<div class="robotCoordinates">
<h1 class="item-timestamp-name">Timestamp:</h1>
<h1 class="item-timestamp-value">{{getFormattedLastRequestedBatteryState()| async}}</h1>
</div>
</div>
<ng-template #errorLoadingData><mat-spinner></mat-spinner>
</ng-template>

+ 23
- 0
software/Frontend/src/app/Robot/robot.component.spec.ts View File

@@ -0,0 +1,23 @@
// import { ComponentFixture, TestBed } from '@angular/core/testing';

// import { RobotComponent } from './robot.component';

// describe('RobotComponent', () => {
// let component: RobotComponent;
// let fixture: ComponentFixture<RobotComponent>;

// beforeEach(async () => {
// await TestBed.configureTestingModule({
// declarations: [ RobotComponent ]
// })
// .compileComponents();

// fixture = TestBed.createComponent(RobotComponent);
// component = fixture.componentInstance;
// fixture.detectChanges();
// });

// it('should create', () => {
// expect(component).toBeTruthy();
// });
// });

+ 53
- 0
software/Frontend/src/app/Robot/robot.component.ts View File

@@ -0,0 +1,53 @@
import { Component } from '@angular/core';
import { ThemePalette } from '@angular/material/core';
import { Observable, map } from 'rxjs';
import { StoreService } from '../Service/store.service';
import { RobotBattery } from '../models/robotBattery.model';

@Component({
selector: 'app-robot',
templateUrl: './robot.component.html',
styleUrls: ['./robot.component.css']
})
export class RobotComponent {
robotBatteryResponse$: Observable<RobotBattery> = this.storeService.currentRobotBattery;
batteryChargingStatus$: Observable<number> = this.robotBatteryResponse$.pipe(map(response => response.Battery));
requestTimestamp$: Observable<string> = this.robotBatteryResponse$.pipe(map(response => response.Timestamp));

constructor(private storeService: StoreService) {
}

getLoadingBarColor(): Observable<ThemePalette> {
return this.batteryChargingStatus$.pipe(
map(chargingStatus => chargingStatus < 20 ? "warn" : "primary")
)
}

getFormattedLastRequestedBatteryState(): Observable<string> {
return this.requestTimestamp$.pipe(
map(timestamp => this.formatLastRequestedBatteryState(timestamp))
)
}

formatLastRequestedBatteryState(timestamp: string): string {
const datePlant = new Date(timestamp);
const currentDate = new Date();
let seconds = ((currentDate.getTime() - datePlant.getTime()) / (1000));

const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);

if (days >= 1) {
return `${days} day${days > 1 ? 's' : ''} ago`;
} else if (hours >= 1) {
return `${hours} hour${hours > 1 ? 's' : ''} ago`;
} else if (minutes >= 1) {
return `${minutes} minute${minutes > 1 ? 's' : ''} ago`;
} else {
seconds = Math.floor(seconds);
return `${seconds} second${seconds !== 1 ? 's' : ''} ago`;
}
}

}

+ 16
- 0
software/Frontend/src/app/Service/Mqtt/mqtt-request.service.spec.ts View File

@@ -0,0 +1,16 @@
// import { TestBed } from '@angular/core/testing';

// import { MqttRequestService } from './mqtt-request.service';

// describe('MqttRequestService', () => {
// let service: MqttRequestService;

// beforeEach(() => {
// TestBed.configureTestingModule({});
// service = TestBed.inject(MqttRequestService);
// });

// it('should be created', () => {
// expect(service).toBeTruthy();
// });
// });

+ 222
- 0
software/Frontend/src/app/Service/Mqtt/mqtt-request.service.ts View File

@@ -0,0 +1,222 @@
import { Injectable } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { BehaviorSubject, Subscription } from 'rxjs';
import { ErrorDialogComponent } from 'src/app/dialog/error-dialog/error-dialog.component';
import { Plant } from 'src/app/models/plant.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 { StoreService } from '../store.service';
import { MqttSetDataService } from './mqtt-set-data.service';
import { MqttService } from './mqtt.service';

@Injectable({
providedIn: 'root'
})
export class MqttRequestService {
private sourceMessage = new BehaviorSubject('');
responseMessage = this.sourceMessage.asObservable();

timerSubscription: Subscription | undefined;

constructor(private mqttService: MqttService,
private mqttSetDataService: MqttSetDataService,
private storeService: StoreService, public dialog: MatDialog) {
//Subscribe to the Topics
this.mqttService.subscribeToTopic("BACKEND/DATA/SENSORDATA_ALL").subscribe(data => {
if (typeof data !== "object") {
const payload = JSON.parse(data) as Plant[];
this.mqttSetDataService.setDataAllPlants(payload);
}
}, err => {
this.dialog.open(ErrorDialogComponent, {
data: err,
width: '400px',
});
console.error('Error:', err);
});

this.mqttService.subscribeToTopic("BACKEND/DATA/ERROR").subscribe(data => {
if (typeof data !== "object") {
const payload = JSON.parse(data) as string;
this.dialog.open(ErrorDialogComponent, {
data: payload,
width: '400px',
});
}
}, err => {
this.dialog.open(ErrorDialogComponent, {
data: err,
width: '400px',
});
console.error('Error:', err);
});

this.mqttService.subscribeToTopic("BACKEND/DATA/SENSORDATA").subscribe(data => {
if (typeof data !== "object") {
const payload = JSON.parse(data) as Plant;
this.mqttSetDataService.setDataPlant(payload);
}
}, err => {
this.dialog.open(ErrorDialogComponent, {
data: err,
width: '400px',
});
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;
this.mqttSetDataService.setRobotBattery(payload);
}
}, err => {
this.dialog.open(ErrorDialogComponent, {
data: err,
width: '400px',
});
console.error('Error:', err);
});

this.mqttService.subscribeToTopic("BACKEND/DATA/ROBOTREADY").subscribe(data => {
if (typeof data !== "object") {
const payload = JSON.parse(data) as boolean;
this.mqttSetDataService.setRobotReady(payload);
}
}, err => {
this.dialog.open(ErrorDialogComponent, {
data: err,
width: '400px',
});
console.error('Error:', err);
});

this.mqttService.subscribeToTopic("BACKEND/DATA/PLANTCOUNT").subscribe(data => {
if (typeof data !== "object") {
const payload = JSON.parse(data) as PlantCount;
this.mqttSetDataService.setPlantCount(payload);
}
}, err => {
this.dialog.open(ErrorDialogComponent, {
data: err,
width: '400px',
});
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
this.publishToPLANTCOUNT();
this.publishToGETBATTERY();
this.publishToROBOTREADY();
this.mqttService.publishToTopic('BACKEND/ACTION/GETALLDATA', JSON.stringify(this.storeService.getAllPlantNames()));
}

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));
}

publishToCONFIGUREPLANT(plant: Plant) {
this.mqttService.publishToTopic('BACKEND/ACTION/CONFIGUREPLANT', JSON.stringify(plant));
}

publishToDELETEPLANT(plantID: number) {
this.mqttService.publishToTopic('BACKEND/ACTION/DELETEPLANT', JSON.stringify(plantID));
}

publishToPLANTCOUNT() {
this.mqttService.publishToTopic('BACKEND/ACTION/PLANTCOUNT');
}

publishToINPUT(message: string) {
message = message.replace(/\s/g, "");
console.log(":" + message + ":");
let msgFound: boolean = false;

if (message === "whereismyrobot") {
msgFound = true;
this.mqttService.publishToTopic("BACKEND/ACTION/GETPOSITION")
this.sourceMessage.next("The data is being collected and you can view it under the 'Robot' tab");
} else if (message === "howmuchbatterydoesmyrobothave") {
msgFound = true;
this.mqttService.publishToTopic('BACKEND/ACTION/GETBATTERY');
this.sourceMessage.next("The data is being collected and you can view it under the 'Robot' tab");
} else if (message === "howaremyplants") {
msgFound = true;
// this.mqttService.publishToTopic('BACKEND/ACTION/DRIVEALL');
this.sourceMessage.next("Robot is driving to all plants");
} else {
this.storeService.getAllPlantNames().subscribe(result =>
result.forEach((element: string) => {
let msgPlantName = "howismyplant".concat(element.toLowerCase())
if (message === msgPlantName) {
msgFound = true;
let PlantName = element;
this.mqttService.publishToTopic('BACKEND/ACTION/DRIVE', JSON.stringify(PlantName));
this.sourceMessage.next("Robot is driving to plant " + element);
}
})
)
}

if (!msgFound) {
this.sourceMessage.next("Please try again");
}

}

onDestroy() {
this.sourceMessage.next('');
}
}

+ 16
- 0
software/Frontend/src/app/Service/Mqtt/mqtt-set-data.service.spec.ts View File

@@ -0,0 +1,16 @@
// import { TestBed } from '@angular/core/testing';

// import { MqttSetDataService } from './mqtt-set-data.service';

// describe('MqttSetDataService', () => {
// let service: MqttSetDataService;

// beforeEach(() => {
// TestBed.configureTestingModule({});
// service = TestBed.inject(MqttSetDataService);
// });

// it('should be created', () => {
// expect(service).toBeTruthy();
// });
// });

+ 36
- 0
software/Frontend/src/app/Service/Mqtt/mqtt-set-data.service.ts View File

@@ -0,0 +1,36 @@
import { Injectable } from '@angular/core';
import { Plant } from 'src/app/models/plant.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 { StoreService } from '../store.service';

@Injectable({
providedIn: 'root'
})
export class MqttSetDataService {

constructor(private storeService: StoreService) { }

setDataAllPlants(data: Plant[]): void {
this.storeService.setDataAllPlants(data);
}

setDataPlant(data: Plant): void {
this.storeService.setDataPlant(data);
}

setRobotBattery(data: RobotBattery): void {
this.storeService.setRobotState(data);
}
setRobotReady(data: boolean): void {
this.storeService.setRobotReady(data);
}
setRobotPosition(data: Position): void {
this.storeService.setRobotPosition(data);
}
setPlantCount(data: PlantCount): void {
this.storeService.setPlantCount(data);
}

}

+ 16
- 0
software/Frontend/src/app/Service/Mqtt/mqtt.service.spec.ts View File

@@ -0,0 +1,16 @@
// import { TestBed } from '@angular/core/testing';

// import { MqttService } from './mqtt.service';

// describe('MqttService', () => {
// let service: MqttService;

// beforeEach(() => {
// TestBed.configureTestingModule({});
// service = TestBed.inject(MqttService);
// });

// it('should be created', () => {
// expect(service).toBeTruthy();
// });
// });

+ 37
- 0
software/Frontend/src/app/Service/Mqtt/mqtt.service.ts View File

@@ -0,0 +1,37 @@
import { Injectable } from '@angular/core';
import { connect, MqttClient } from 'mqtt/dist/mqtt';
import { Observable } from 'rxjs';

@Injectable({
providedIn: 'root'
})
export class MqttService {

private client: MqttClient;

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' });
}

public subscribeToTopic(topic: string): Observable<any> {
return new Observable(observer => {
this.client.subscribe(topic, (err, granted) => {
if (err) {
observer.error(err);
} else {
observer.next(granted);
}
});
this.client.on('message', (t, m) => {
if (t === topic) {
observer.next(m.toString());
}
});
});
}

public publishToTopic(topic: string, message?: any): void {
this.client.publish(topic, message);
}
}

+ 16
- 0
software/Frontend/src/app/Service/store.service.spec.ts View File

@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';

import { StoreService } from './store.service';

describe('StoreService', () => {
let service: StoreService;

beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(StoreService);
});

it('should be created', () => {
expect(service).toBeTruthy();
});
});

+ 96
- 0
software/Frontend/src/app/Service/store.service.ts View File

@@ -0,0 +1,96 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable, map, of } from 'rxjs';
import { Plant } from '../models/plant.model';
import { PlantCount } from '../models/plantCount.model';
import { Position } from '../models/position.model';
import { RobotBattery } from '../models/robotBattery.model';

@Injectable({
providedIn: 'root'
})
export class StoreService {
positionRobot: Position;
plantCount: PlantCount;

private plants: BehaviorSubject<Plant[]> = new BehaviorSubject<Plant[]>(undefined);
currentPlants: Observable<Plant[]> = this.plants.asObservable();

private robotReady = new BehaviorSubject<boolean>(undefined);
currentRobotReady = this.robotReady.asObservable();

private robotBattery = new BehaviorSubject<RobotBattery>(undefined);
currentRobotBattery = this.robotBattery.asObservable();

constructor() {
}

getPlant(name: string): Observable<Plant> {
return this.currentPlants.pipe(map(response => {
for (let plant of response) {
if (plant.PlantName.toLocaleLowerCase() === name.toLocaleLowerCase()) {
return plant;
}
}
return null;
}))
}

getAllPlantNames(): Observable<string[]> {
return this.currentPlants.pipe(map(response => response?.map(response => response.PlantName)));
}

getIDsWithoutPlant(plants: Plant[]): Observable<number[]> {
let listOfID: number[] = [];
let listOfIDWithoutPlant: number[] = [];

if (plants.length > 0) {
//Write ID's of the Plants in a List
plants.forEach(plant => {
listOfID.push(plant.PlantID);
});

//Sort the List of ID's
listOfID.sort();

//Write the ID's without Plants in a List
for (let index = 0; index < this.plantCount.MaxCount; index++) { //Noch unsicher.... evtl. die länge von wo anders holen..
if (!listOfID.includes(index + 1)) {
listOfIDWithoutPlant.push(index + 1);
}
}
return of(listOfIDWithoutPlant);
} else {
for (let index = 0; index < this.plantCount.MaxCount; index++) {
listOfIDWithoutPlant.push(index + 1);
}
return of(listOfIDWithoutPlant);
}
}

//Set data
setDataAllPlants(data: Plant[]): void {
this.plants.next(data);
}

setDataPlant(data: Plant): void {
this.plants.subscribe(response => {
response.forEach(plant => {
if (plant.PlantID === data.PlantID) {
plant = data;
}
})
})
}
setRobotState(data: RobotBattery): void {
this.robotBattery.next(data);
}
setRobotReady(data: boolean): void {
this.robotReady.next(data);
}
setRobotPosition(data: Position): void {
this.positionRobot = data;
}
setPlantCount(data: PlantCount): void {
this.plantCount = data;
}
}

+ 10
- 0
software/Frontend/src/app/app-routing.module.ts View File

@@ -0,0 +1,10 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

const routes: Routes = [];

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

+ 44
- 0
software/Frontend/src/app/app.component.css View File

@@ -0,0 +1,44 @@
.container {
margin: 50px;
}

.icon-menu {
transform: scale(2);
}

span {
font-size: 1.5rem;
font-family: Cormorant;
}

header {
border: 0.125rem;
border-style: solid;
border-radius: 1rem;
border-color: #89bf54;
box-shadow: 0.2rem 0.2rem 0.2rem silver;
padding: 4px;
display: grid;
grid-template-columns: 20% 60% 20%;
font-family: Cormorant;

.menu-item {
grid-column: 1/2;
justify-self: start;
align-self: center;
}

.header-item {
grid-column: 2/3;
justify-self: center;
align-self: end;
font-size: 3rem;
}

.img-item {
grid-column: 3/4;
justify-self: end;
width: 15rem;
height: 3rem;
}
}

+ 36
- 0
software/Frontend/src/app/app.component.html View File

@@ -0,0 +1,36 @@
<html class="container">

<header>
<div class="menu-item">
<button mat-icon-button [matMenuTriggerFor]="menu" aria-label="Example icon-button with a menu">
<mat-icon class="icon-menu">menu</mat-icon>
</button>
<mat-menu #menu="matMenu">
<button mat-menu-item routerLink="/home" (click)="onMenuSelect('Home')">
<mat-icon>micro</mat-icon>
<span>Home</span>
</button>
<button mat-menu-item routerLink="/map" (click)="onMenuSelect('Map')">
<mat-icon>public</mat-icon>
<span>Map</span>
</button>
<button mat-menu-item routerLink="plants" (click)="onMenuSelect('Plants')">
<mat-icon>grass</mat-icon>
<span>Plants</span>
</button>
<button mat-menu-item routerLink="robot" (click)="onMenuSelect('Robot')">
<mat-icon>android</mat-icon>
<span>Robot</span>
</button>
</mat-menu>
</div>
<p class="header-item">{{header}}</p>
<img class="img-item" src="assets/images/logo.png" alt="not available">
</header>


<body>
<router-outlet></router-outlet>
</body>

</html>

+ 59
- 0
software/Frontend/src/app/app.component.spec.ts View File

@@ -0,0 +1,59 @@
import { Location } from '@angular/common';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MatMenuModule } from '@angular/material/menu';
import { MqttRequestService } from './Service/Mqtt/mqtt-request.service';
import { AppComponent } from './app.component';

describe('AppComponent', () => {
let component: AppComponent;
let fixture: ComponentFixture<AppComponent>;
let mqttRequestServiceSpy: jasmine.SpyObj<MqttRequestService>;
let locationStub: Partial<Location>;

beforeEach(async () => {
const mqttRequestServiceMock = jasmine.createSpyObj('MqttRequestService', ['someMethod']);

locationStub = {
path: () => '/home'
};

await TestBed.configureTestingModule({
imports: [MatMenuModule],
declarations: [AppComponent],
providers: [
{ provide: MqttRequestService, useValue: mqttRequestServiceMock },
{ provide: Location, useValue: locationStub }
]
}).compileComponents();
});

beforeEach(() => {
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;

mqttRequestServiceSpy = TestBed.inject(MqttRequestService) as jasmine.SpyObj<MqttRequestService>;

fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});

it('should set header to "Home" if the route is empty', () => {
expect(component.header).toBe('Home');
});

it('should set header to the capitalized route value', () => {
locationStub.path = () => '/about';
const componentNew = new AppComponent(mqttRequestServiceSpy, locationStub as Location);

expect(componentNew.header).toBe('About');
});

it('should update header when onMenuSelect is called', () => {
component.onMenuSelect('Map');

expect(component.header).toBe('Map');
});
});

+ 27
- 0
software/Frontend/src/app/app.component.ts View File

@@ -0,0 +1,27 @@
import { Location } from '@angular/common';
import { Component } from '@angular/core';

import { MqttRequestService } from './Service/Mqtt/mqtt-request.service';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
header: string;
constructor(private mqttRequestService: MqttRequestService, private location: Location) {
const actualRoute = this.location.path().slice(1);
const lastParam = actualRoute.charAt(0).toUpperCase() + actualRoute.slice(1);
if (lastParam === "") {
this.onMenuSelect('Home');
} else {
this.onMenuSelect(lastParam);
}
}

onMenuSelect(header: string) {
this.header = header;
}
}


+ 87
- 0
software/Frontend/src/app/app.module.ts View File

@@ -0,0 +1,87 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { ScrollingModule } from '@angular/cdk/scrolling';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatBadgeModule } from '@angular/material/badge';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatMenuModule } from '@angular/material/menu';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './Home/home.component';
import { InputComponent } from './Home/input/input.component';
import { OutputComponent } from './Home/output/output.component';
import { MapComponent } from './Map/map.component';
import { PlantCardComponent } from './Plants/plant-card/plant-card.component';
import { PlantsComponent } from './Plants/plants.component';
import { RobotComponent } from './Robot/robot.component';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { AddPlantDialogComponent } from './dialog/add-plant-dialog/add-plant-dialog.component';
import { ConfigurePlantDialogComponent } from './dialog/configure-plant-dialog/configure-plant-dialog.component';
import { ErrorDialogComponent } from './dialog/error-dialog/error-dialog.component';
import { MapPlantDialogComponent } from './dialog/map-plant-dialog/map-plant-dialog.component';
import { DeleteConfirmDialogComponent } from './dialog/delete-confirm-dialog/delete-confirm-dialog.component';


const appRoutes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'home', component: HomeComponent },
{ path: 'map', component: MapComponent },
{ path: 'plants', component: PlantsComponent },
{ path: 'robot', component: RobotComponent }
];

@NgModule({
declarations: [
AppComponent,
HomeComponent,
MapComponent,
InputComponent,
OutputComponent,
PlantsComponent,
PlantCardComponent,
RobotComponent,
ConfigurePlantDialogComponent,
AddPlantDialogComponent,
MapPlantDialogComponent,
ErrorDialogComponent,
DeleteConfirmDialogComponent
],
imports: [
BrowserModule,
AppRoutingModule,
MatMenuModule,
MatIconModule,
MatButtonModule,
HttpClientModule,
BrowserAnimationsModule,
MatBadgeModule,
RouterModule.forRoot(appRoutes),
MatFormFieldModule,
FormsModule,
MatInputModule,
MatCardModule,
ScrollingModule,
MatProgressBarModule,
MatAutocompleteModule,
ReactiveFormsModule,
MatDialogModule,
MatSelectModule,
MatProgressSpinnerModule
],
providers: [],
bootstrap: [AppComponent],
exports: [ConfigurePlantDialogComponent, AddPlantDialogComponent, MapPlantDialogComponent, ErrorDialogComponent]
})
export class AppModule { }

+ 78
- 0
software/Frontend/src/app/dialog/add-plant-dialog/add-plant-dialog.component.css View File

@@ -0,0 +1,78 @@
.item-header-content {
display: grid;
grid-template-columns: 11.25rem 6.25rem;
grid-template-rows: 20% 80%;
justify-content: start;
align-content: start;
}

.item-header {
grid-column: 1/-1;
grid-row: 1/2;
justify-self: start;
align-self: center;
font-size: 1.5625rem;
margin-bottom: 1rem;
color: blue;
display: grid;
grid-template-columns: 11.25rem 6.25rem;
}

.item-header-text {
grid-column: 1/2;
justify-self: center;
align-self: center;
}

.item-header-button {
grid-column: 2/3;
justify-self: end;
background-color: white;
transform: scale(0.8);
}

.icon-language {
color: black;
transform: scale(1.6);
}

.item-header-button:hover {
background-color: silver;
}

.item-Measurements-IconsAndNames {
grid-column: 1/2;
grid-row: 2/3;
justify-self: start;
align-self: center;
font-size: 1.125rem;
}

.item-Measurements-Values {
grid-column: 2/3;
justify-self: start;
align-self: center;
}

.item-IMG {
justify-self: center;
align-self: center;
}

p {
margin: 0;
}

.IMG-Plants {
border-radius: 0.5rem;
width: 11.25rem;
height: 9.375rem;
margin: 0rem 0.3125rem;
}

.icons-measurements {
width: 1rem;
height: 0.75rem;
margin: 0rem 0.3125rem;
margin-top: 0.4rem;
}

+ 43
- 0
software/Frontend/src/app/dialog/add-plant-dialog/add-plant-dialog.component.html View File

@@ -0,0 +1,43 @@
<div class="item-header-content">
<div class="item-header">
<p class="item-header-text">Add Plant:</p>
<button mat-fab class="item-header-button" (click)="onOpenMap()"><mat-icon
class="icon-language">language</mat-icon></button>
</div>
<div class="item-Measurements-IconsAndNames">
<img class="icons-measurements" src="assets/images/Icons-Card/id_icon.png" alt="not available">
ID: <br><br><br>
<img class="icons-measurements" src="assets/images/Icons-Card/name_icon.png" alt="not available">
Name: <br>
<img class="icons-measurements" src="assets/images/Icons-Card/air_humidity_icon.png" alt="not available">
Air Humidity: <br>
<img class="icons-measurements" src="assets/images/Icons-Card/air_temperature_icon.png" alt="not available">
Air Temperature: <br>
<img class="icons-measurements" src="assets/images/Icons-Card/soil_moisture_icon.png" alt="not available">
Soil Moisture: <br>
<img class="icons-measurements" src="assets/images/Icons-Card/brightness_icon.png" alt="not available">
Brightness:<br>
<!-- <img class="icons-measurements" src="assets/images/Icons-Card/Timestamp_icon.png" alt="not available">
Timestamp: -->
</div>
<div class="item-Measurements-Values">
<mat-form-field class="item-option" appearance="fill">
<mat-label>Choose an ID</mat-label>
<mat-select [(value)]="selected" (closed)="onOptionSelected(selected)">
<mat-option *ngFor="let option of options$ | async" [value]="option">
{{option}}
</mat-option>
</mat-select>
</mat-form-field>
<input type="text" id="inputName" maxlength="15">
<input type="number" id="inputAirTemperature" max="100">
<input type="number" id="inputAirHumidity" max="100">
<input type="number" id="inputSoilMoisture" max="100">
<input type="number" id="inputBrightness" max="100">
</div>
</div>
<div mat-dialog-actions>
<button mat-button mat-dialog-close>No</button>
<button mat-button mat-dialog-close cdkFocusInitial (click)="onSaveDialog()"
[disabled]="buttonSavedisabled">Save</button>
</div>

+ 172
- 0
software/Frontend/src/app/dialog/add-plant-dialog/add-plant-dialog.component.spec.ts View File

@@ -0,0 +1,172 @@
// import { ComponentFixture, TestBed } from '@angular/core/testing';
// import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog';
// import { of } from 'rxjs';
// import { MqttRequestService } from 'src/app/Service/Mqtt/mqtt-request.service';
// import { StoreService } from 'src/app/Service/store.service';
// import { Plant } from 'src/app/models/plant.model';
// import { AddPlantDialogComponent } from './add-plant-dialog.component';
// const plantMock1: Plant = {
// AirTemperature: 25,
// AirHumidity: 60,
// SoilMoisture: 50,
// Brightness: 80,
// PlantID: 1,
// Timestamp: "2023-06-05T12:00:00",
// MeasurementID: "abcd1234",
// PlantName: "Plant1",
// };
// const plantMock2: Plant = {
// AirTemperature: 22,
// AirHumidity: 60,
// SoilMoisture: 50,
// Brightness: 80,
// PlantID: 1,
// Timestamp: "2023-06-05T12:00:00",
// MeasurementID: "abcd1234",
// PlantName: "Plant2",
// };
// const plantMock3: Plant = {
// AirTemperature: 21,
// AirHumidity: 60,
// SoilMoisture: 50,
// Brightness: 80,
// PlantID: 1,
// Timestamp: "2023-06-05T12:00:00",
// MeasurementID: "abcd1234",
// PlantName: "Plant3",
// };
// describe('AddPlantDialogComponent', () => {
// let component: AddPlantDialogComponent;
// let fixture: ComponentFixture<AddPlantDialogComponent>;
// let dialogRefSpyObj: jasmine.SpyObj<MatDialogRef<AddPlantDialogComponent>>;
// let dialogSpy: jasmine.SpyObj<MatDialog>;
// let mqttRequestServiceSpy: jasmine.SpyObj<MqttRequestService>;
// let storeServiceSpy: jasmine.SpyObj<StoreService>;

// beforeEach(async () => {
// const dialogRefSpy = jasmine.createSpyObj('MatDialogRef', ['close']);
// const dialogSpyObj = jasmine.createSpyObj('MatDialog', ['open']);
// const mqttRequestServiceSpyObj = jasmine.createSpyObj('MqttRequestService', [
// 'publishToPLANTCOUNT',
// 'publishToNEWPLANT',
// 'publishToGETALLDATA',
// ]);
// const storeServiceSpyObj = jasmine.createSpyObj('StoreService', [
// 'currentPlants',
// 'getIDsWithoutPlant',
// 'getAllPlantNames',
// ]);

// await TestBed.configureTestingModule({
// declarations: [AddPlantDialogComponent],
// providers: [
// { provide: MatDialogRef, useValue: dialogRefSpy },
// { provide: MAT_DIALOG_DATA, useValue: {} },
// { provide: MatDialog, useValue: dialogSpyObj },
// { provide: MqttRequestService, useValue: mqttRequestServiceSpyObj },
// { provide: StoreService, useValue: storeServiceSpyObj },
// ],
// }).compileComponents();
// });

// beforeEach(() => {
// fixture = TestBed.createComponent(AddPlantDialogComponent);
// component = fixture.componentInstance;
// dialogRefSpyObj = TestBed.inject(MatDialogRef) as jasmine.SpyObj<MatDialogRef<AddPlantDialogComponent>>;
// dialogSpy = TestBed.inject(MatDialog) as jasmine.SpyObj<MatDialog>;
// mqttRequestServiceSpy = TestBed.inject(MqttRequestService) as jasmine.SpyObj<MqttRequestService>;
// storeServiceSpy = TestBed.inject(StoreService) as jasmine.SpyObj<StoreService>;
// });

// it('should initialize with buttonSavedisabled set to true', () => {
// // Assert
// expect(component.buttonSavedisabled).toBe(true);
// });

// it('should call publishToPLANTCOUNT method and retrieve options', () => {
// // Arrange
// const currentPlantsMock = of([plantMock1, plantMock2, plantMock3]);
// const getIDsWithoutPlantMock = jasmine.createSpy().and.returnValue(of([4, 5, 6]));
// const getAllPlantNamesMock = jasmine.createSpy().and.returnValue(of(['Plant1', 'Plant2', 'Plant3']));

// // Act
// storeServiceSpy.currentPlants = currentPlantsMock;
// storeServiceSpy.getIDsWithoutPlant = getIDsWithoutPlantMock;
// storeServiceSpy.getAllPlantNames = getAllPlantNamesMock;
// fixture.detectChanges();

// // Assert
// expect(mqttRequestServiceSpy.publishToPLANTCOUNT).toHaveBeenCalled();
// expect(storeServiceSpy.getIDsWithoutPlant).toHaveBeenCalledWith([plantMock1, plantMock2, plantMock3]);
// expect(storeServiceSpy.getAllPlantNames).toHaveBeenCalled();
// expect(component.options$).toEqual(of([4, 5, 6]));
// expect(component.plantNames).toEqual(['Plant1', 'Plant2', 'Plant3']);
// });

// // it('should open the error dialog if plant input fails', () => {
// // // Arrange
// // const plantInputString = 'Plant Name is empty';

// // // Act
// // component.plantInputTestFails = jasmine.createSpy().and.returnValue(plantInputString);
// // component.onSaveDialog();

// // // Assert
// // expect(dialogSpy.open).toHaveBeenCalledWith(ErrorDialogComponent, {
// // data: 'Error: ' + plantInputString,
// // width: '400px',
// // });
// // expect(mqttRequestServiceSpy.publishToNEWPLANT).not.toHaveBeenCalled();
// // expect(mqttRequestServiceSpy.publishToGETALLDATA).not.toHaveBeenCalled();
// // expect(window.location.reload).not.toHaveBeenCalled();
// // });

// // it('should call the MQTT request service and reload the page if plant input is valid', () => {
// // // Arrange
// // const plantInputString = '';

// // // Act
// // component.plantInputTestFails = jasmine.createSpy().and.returnValue(plantInputString);
// // component.onSaveDialog();

// // // Assert
// // expect(dialogSpy.open).not.toHaveBeenCalled();
// // expect(mqttRequestServiceSpy.publishToNEWPLANT).toHaveBeenCalledWith(component.plant);
// // expect(mqttRequestServiceSpy.publishToGETALLDATA).toHaveBeenCalled();
// // expect(window.location.reload).toHaveBeenCalled();
// // });

// // it('should open the map plant dialog', () => {
// // // Act
// // component.onOpenMap();

// // // Assert
// // expect(dialogSpy.open).toHaveBeenCalledWith(MapPlantDialogComponent, {
// // width: '400px',
// // height: '400px',
// // });
// // });

// // it('should set buttonSavedisabled to false when option is selected within the range', () => {
// // // Arrange
// // component.buttonSavedisabled = true;

// // // Act
// // component.onOptionSelected(5);

// // // Assert
// // expect(component.buttonSavedisabled).toBeFalse();
// // });

// // it('should keep buttonSavedisabled as true when option is selected outside the range', () => {
// // // Arrange
// // component.buttonSavedisabled = true;

// // // Act
// // component.onOptionSelected(10);

// // // Assert
// // expect(component.buttonSavedisabled).toBeTrue();
// // });

// });

+ 100
- 0
software/Frontend/src/app/dialog/add-plant-dialog/add-plant-dialog.component.ts View File

@@ -0,0 +1,100 @@
import { Component, Inject, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog';
import { Observable } from 'rxjs';
import { MqttRequestService } from 'src/app/Service/Mqtt/mqtt-request.service';
import { StoreService } from 'src/app/Service/store.service';
import { Plant } from 'src/app/models/plant.model';
import { ErrorDialogComponent } from '../error-dialog/error-dialog.component';
import { MapPlantDialogComponent } from '../map-plant-dialog/map-plant-dialog.component';

@Component({
selector: 'app-add-plant-dialog',
templateUrl: './add-plant-dialog.component.html',
styleUrls: ['./add-plant-dialog.component.css']
})
export class AddPlantDialogComponent implements OnInit {
selected: number;
options$: Observable<number[]>;
plantNames: string[];
plant = {} as Plant;
buttonSavedisabled: boolean;
number: number[];

constructor(public dialogRef: MatDialogRef<AddPlantDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
public dialog: MatDialog,
private storeService: StoreService,
private mqttRequestService: MqttRequestService) {
}

ngOnInit(): void {
this.buttonSavedisabled = true;
this.mqttRequestService.publishToPLANTCOUNT();
this.storeService.currentPlants.subscribe(response => {
this.options$ = this.storeService.getIDsWithoutPlant(response);
})
this.storeService.getAllPlantNames().subscribe(result => {
this.plantNames = result
});
}

onSaveDialog() {
this.plant.PlantName = (<HTMLInputElement>document.getElementById("inputName")).value;
this.plant.AirTemperature = +(<HTMLInputElement>document.getElementById("inputAirTemperature")).value;
this.plant.AirHumidity = +(<HTMLInputElement>document.getElementById("inputAirHumidity")).value;
this.plant.SoilMoisture = +(<HTMLInputElement>document.getElementById("inputSoilMoisture")).value;
this.plant.Brightness = +(<HTMLInputElement>document.getElementById("inputBrightness")).value;
this.plant.PlantID = this.selected;

const plantInputString: string = this.plantInputTestFails();

if (plantInputString) {
this.dialog.open(ErrorDialogComponent, {
data: "Error: " + plantInputString,
width: '400px',
});
} else {
this.mqttRequestService.publishToNEWPLANT(this.plant);
this.mqttRequestService.publishToGETALLDATA();
window.location.reload();
}
}

plantInputTestFails(): string {

if (this.plantNames.indexOf(this.plant.PlantName) > -1) {
return "Plant Name already exists";
}
if (this.plant.PlantName === "") {
return "Plant Name is empty";
}
if (this.plant.AirTemperature > 100) {
return "Air Temperature is to high";
}
if (this.plant.AirHumidity > 100) {
return "Air Humidity is to high";
}
if (this.plant.SoilMoisture > 100) {
return "Soil Moisture is to high";
}
if (this.plant.Brightness > 100) {
return "Brightness is to high";
}
return ""; //Empty string is a Boolean false!
}

onOpenMap() {
this.dialog.open(MapPlantDialogComponent, {
width: '400px',
height: '400px'
});
}

onOptionSelected(numb: number) {
let num: number = numb;
if (num < 7 && num > 0) { //TODOO anzahl der Pflanzen IDS variable machen statt 7 evtl. eine variable
this.buttonSavedisabled = false;
}
}

}

+ 62
- 0
software/Frontend/src/app/dialog/configure-plant-dialog/configure-plant-dialog.component.css View File

@@ -0,0 +1,62 @@
.item-header-content {
display: grid;
grid-template-columns: 11.25rem 6rem 3rem;
grid-template-rows: 20% 80%;
justify-content: start;
align-content: start;
}

.item-header {
grid-column: 1/-1;
grid-row: 1/2;
justify-self: start;
align-self: center;
font-size: 1.6rem;
margin-bottom: 1rem;
color: blue;
}

.item-Measurements-IconsAndNames {
grid-column: 1/2;
grid-row: 2/3;
justify-self: start;
align-self: center;
}

.item-Measurements-Values {
grid-column: 2/3;
justify-self: start;
align-self: center;
}

.item-Measurements-Units {
grid-column: 3/4;
justify-self: start;
align-self: center;
}

.item-IMG {
justify-self: center;
align-self: center;
}

p {
margin: 0;
}

.IMG-Plants {
border-radius: 0.5rem;
width: 11.25rem;
height: 9.375rem;
margin: 0rem 0.3125rem;
}

.icons-measurements {
width: 1rem;
height: 0.75rem;
margin: 0rem 0.3125rem;
}

input {
width: 5rem;
}

+ 34
- 0
software/Frontend/src/app/dialog/configure-plant-dialog/configure-plant-dialog.component.html View File

@@ -0,0 +1,34 @@
<div class="item-header-content">
<div class="item-header">
<p>Configure Plant: {{plant.PlantName}}</p>
</div>
<div class="item-Measurements-IconsAndNames">
<img class="icons-measurements" src="assets/images/Icons-Card/air_humidity_icon.png" alt="not available">
Air Humidity: <br>
<img class="icons-measurements" src="assets/images/Icons-Card/air_temperature_icon.png" alt="not available">
Air Temperature: <br>
<img class="icons-measurements" src="assets/images/Icons-Card/soil_moisture_icon.png" alt="not available">
Soil Moisture: <br>
<img class="icons-measurements" src="assets/images/Icons-Card/brightness_icon.png" alt="not available">
Brightness:<br>
<!-- <img class="icons-measurements" src="assets/images/Icons-Card/Timestamp_icon.png" alt="not available">
Timestamp: -->
</div>
<div class="item-Measurements-Values">
<input type="number" [value]=plant.AirTemperature id="inputAirTemperature">
<input type="number" [value]=plant.AirHumidity id="inputAirHumidity">
<input type="number" [value]=plant.SoilMoisture id="inputSoilMoisture">
<input type="number" [value]=plant.Brightness id="inputBrightness">
<!-- <input type="datetime" [value]=plant.Timestamp id="inputTimestamp"> -->
</div>
<div class="item-Measurements-Units">
<p>%</p>
<p>°C</p>
<p>%</p>
<p>Lux</p>
</div>
</div>
<div mat-dialog-actions>
<button mat-button mat-dialog-close>No</button>
<button mat-button mat-dialog-close cdkFocusInitial (click)="onSaveDialog()">Save</button>
</div>

+ 23
- 0
software/Frontend/src/app/dialog/configure-plant-dialog/configure-plant-dialog.component.spec.ts View File

@@ -0,0 +1,23 @@
// import { ComponentFixture, TestBed } from '@angular/core/testing';

// import { ConfigurePlantDialogComponent } from './configure-plant-dialog.component';

// describe('ConfigurePlantDialogComponent', () => {
// let component: ConfigurePlantDialogComponent;
// let fixture: ComponentFixture<ConfigurePlantDialogComponent>;

// beforeEach(async () => {
// await TestBed.configureTestingModule({
// declarations: [ ConfigurePlantDialogComponent ]
// })
// .compileComponents();

// fixture = TestBed.createComponent(ConfigurePlantDialogComponent);
// component = fixture.componentInstance;
// fixture.detectChanges();
// });

// it('should create', () => {
// expect(component).toBeTruthy();
// });
// });

+ 60
- 0
software/Frontend/src/app/dialog/configure-plant-dialog/configure-plant-dialog.component.ts View File

@@ -0,0 +1,60 @@
import { Component, Inject, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog';
import { MqttRequestService } from 'src/app/Service/Mqtt/mqtt-request.service';
import { Plant } from 'src/app/models/plant.model';
import { ErrorDialogComponent } from '../error-dialog/error-dialog.component';

@Component({
selector: 'app-configure-plant-dialog',
templateUrl: './configure-plant-dialog.component.html',
styleUrls: ['./configure-plant-dialog.component.css']
})
export class ConfigurePlantDialogComponent implements OnInit {
plant: Plant;

constructor(public dialogRef: MatDialogRef<ConfigurePlantDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
private mqttRequestService: MqttRequestService,
public dialog: MatDialog) {
this.plant = data.plant;
}

ngOnInit(): void {
}

onSaveDialog() {
this.plant.AirTemperature = +(<HTMLInputElement>document.getElementById("inputAirTemperature")).value;
this.plant.AirHumidity = +(<HTMLInputElement>document.getElementById("inputAirHumidity")).value;
this.plant.SoilMoisture = +(<HTMLInputElement>document.getElementById("inputSoilMoisture")).value;
this.plant.Brightness = +(<HTMLInputElement>document.getElementById("inputBrightness")).value;

const plantInputString: string = this.plantInputTestFails();
if (plantInputString) {
this.dialog.open(ErrorDialogComponent, {
data: "Error: " + plantInputString,
width: '400px',
});
} else {
//Request to the Backend, to change the data of the plant
this.mqttRequestService.publishToCONFIGUREPLANT(this.plant);
this.mqttRequestService.publishToGETALLDATA();
window.location.reload();
}
}

plantInputTestFails(): string {
if (this.plant.AirTemperature > 100) {
return "Air Temperature is to high";
}
if (this.plant.AirHumidity > 100) {
return "Air Humidity is to high";
}
if (this.plant.SoilMoisture > 100) {
return "Soil Moisture is to high";
}
if (this.plant.Brightness > 100) {
return "Brightness is to high";
}
return ""; //Empty string is a Boolean false!
}
}

+ 10
- 0
software/Frontend/src/app/dialog/delete-confirm-dialog/delete-confirm-dialog.component.css View File

@@ -0,0 +1,10 @@
:host ::ng-deep {
.mat-dialog-title {
color: red;
}

.mat-button {
background-color: ghostwhite;
}

}

+ 8
- 0
software/Frontend/src/app/dialog/delete-confirm-dialog/delete-confirm-dialog.component.html View File

@@ -0,0 +1,8 @@
<div class="container">
<p mat-dialog-title>Delete Plant: {{plant.PlantName}}</p>
<p mat-dialog-content>Soll die Pflanze "{{plant.PlantName}}" tatsächlich gelöscht werden?</p>
<div mat-dialog-actions>
<button mat-button mat-dialog-close>No</button>
<button mat-button mat-dialog-close cdkFocusInitial (click)="onDelete()" color="warn">Delete</button>
</div>
</div>

+ 23
- 0
software/Frontend/src/app/dialog/delete-confirm-dialog/delete-confirm-dialog.component.spec.ts View File

@@ -0,0 +1,23 @@
// import { ComponentFixture, TestBed } from '@angular/core/testing';

// import { DeleteConfirmDialogComponent } from './delete-confirm-dialog.component';

// describe('DeleteConfirmDialogComponent', () => {
// let component: DeleteConfirmDialogComponent;
// let fixture: ComponentFixture<DeleteConfirmDialogComponent>;

// beforeEach(async () => {
// await TestBed.configureTestingModule({
// declarations: [ DeleteConfirmDialogComponent ]
// })
// .compileComponents();

// fixture = TestBed.createComponent(DeleteConfirmDialogComponent);
// component = fixture.componentInstance;
// fixture.detectChanges();
// });

// it('should create', () => {
// expect(component).toBeTruthy();
// });
// });

+ 24
- 0
software/Frontend/src/app/dialog/delete-confirm-dialog/delete-confirm-dialog.component.ts View File

@@ -0,0 +1,24 @@
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { MqttRequestService } from 'src/app/Service/Mqtt/mqtt-request.service';
import { Plant } from 'src/app/models/plant.model';
import { ConfigurePlantDialogComponent } from '../configure-plant-dialog/configure-plant-dialog.component';

@Component({
selector: 'app-delete-confirm-dialog',
templateUrl: './delete-confirm-dialog.component.html',
styleUrls: ['./delete-confirm-dialog.component.css']
})
export class DeleteConfirmDialogComponent {
plant: Plant;
constructor(public dialogRef: MatDialogRef<ConfigurePlantDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: Plant, private mqttRequestService: MqttRequestService) {
this.plant = data;
}


onDelete() {
this.mqttRequestService.publishToDELETEPLANT(this.plant.PlantID);
}

}

+ 10
- 0
software/Frontend/src/app/dialog/error-dialog/error-dialog.component.css View File

@@ -0,0 +1,10 @@
:host ::ng-deep {
.mat-dialog-title {
color: red;
}

.mat-button {
background-color: ghostwhite;
}

}

+ 7
- 0
software/Frontend/src/app/dialog/error-dialog/error-dialog.component.html View File

@@ -0,0 +1,7 @@
<div class="container">
<p mat-dialog-title>Error</p>
<p mat-dialog-content>{{text}}</p>
<div mat-dialog-actions>
<button mat-button mat-dialog-close (click)="onClose()">Close</button>
</div>
</div>

+ 23
- 0
software/Frontend/src/app/dialog/error-dialog/error-dialog.component.spec.ts View File

@@ -0,0 +1,23 @@
// import { ComponentFixture, TestBed } from '@angular/core/testing';

// import { ErrorDialogComponent } from './error-dialog.component';

// describe('ErrorDialogComponent', () => {
// let component: ErrorDialogComponent;
// let fixture: ComponentFixture<ErrorDialogComponent>;

// beforeEach(async () => {
// await TestBed.configureTestingModule({
// declarations: [ ErrorDialogComponent ]
// })
// .compileComponents();

// fixture = TestBed.createComponent(ErrorDialogComponent);
// component = fixture.componentInstance;
// fixture.detectChanges();
// });

// it('should create', () => {
// expect(component).toBeTruthy();
// });
// });

+ 25
- 0
software/Frontend/src/app/dialog/error-dialog/error-dialog.component.ts View File

@@ -0,0 +1,25 @@
import { Component, Inject, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { ConfigurePlantDialogComponent } from '../configure-plant-dialog/configure-plant-dialog.component';

@Component({
selector: 'app-error-dialog',
templateUrl: './error-dialog.component.html',
styleUrls: ['./error-dialog.component.css']
})
export class ErrorDialogComponent implements OnInit {

text: string = "";
constructor(public dialogRef: MatDialogRef<ConfigurePlantDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any) {
this.text = data;
}

ngOnInit(): void {
}

onClose() {
window.location.reload();
}

}

+ 27
- 0
software/Frontend/src/app/dialog/map-plant-dialog/map-plant-dialog.component.css View File

@@ -0,0 +1,27 @@
.container {
width: 18.75rem;
height: 21.875rem;
display: grid;
grid-template-rows: 15% 70% 15%;
justify-content: start;
align-content: start;
}

.item-header {
grid-row: 1/2;
justify-self: center;
font-size: 1.6rem;
color: red;
}

.item-img {
grid-row: 2/3;
justify-self: center;
width: 21.875rem;
height: 15.625rem;
}

.item-button {
grid-row: 3/4;
justify-self: center;
}

+ 7
- 0
software/Frontend/src/app/dialog/map-plant-dialog/map-plant-dialog.component.html View File

@@ -0,0 +1,7 @@
<div class="container">
<p class="item-header" mat-dialog-title>Map</p>
<img class="item-img" src="assets/images/gruppe1.jpg" alt="not available">
<div class="item-button" mat-dialog-actions>
<button mat-button mat-dialog-close>Close</button>
</div>
</div>

+ 23
- 0
software/Frontend/src/app/dialog/map-plant-dialog/map-plant-dialog.component.spec.ts View File

@@ -0,0 +1,23 @@
// import { ComponentFixture, TestBed } from '@angular/core/testing';

// import { MapPlantDialogComponent } from './map-plant-dialog.component';

// describe('MapPlantDialogComponent', () => {
// let component: MapPlantDialogComponent;
// let fixture: ComponentFixture<MapPlantDialogComponent>;

// beforeEach(async () => {
// await TestBed.configureTestingModule({
// declarations: [ MapPlantDialogComponent ]
// })
// .compileComponents();

// fixture = TestBed.createComponent(MapPlantDialogComponent);
// component = fixture.componentInstance;
// fixture.detectChanges();
// });

// it('should create', () => {
// expect(component).toBeTruthy();
// });
// });

+ 15
- 0
software/Frontend/src/app/dialog/map-plant-dialog/map-plant-dialog.component.ts View File

@@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';

@Component({
selector: 'app-map-plant-dialog',
templateUrl: './map-plant-dialog.component.html',
styleUrls: ['./map-plant-dialog.component.css']
})
export class MapPlantDialogComponent implements OnInit {

constructor() { }

ngOnInit(): void {
}

}

+ 10
- 0
software/Frontend/src/app/models/plant.model.ts View File

@@ -0,0 +1,10 @@
export interface Plant {
AirTemperature: number;
AirHumidity: number;
SoilMoisture: number;
Brightness: number;
PlantID: number;
Timestamp: string;
MeasurementID: string;
PlantName: string;
}

+ 4
- 0
software/Frontend/src/app/models/plantCount.model.ts View File

@@ -0,0 +1,4 @@
export interface PlantCount {
CurrentCount: number;
MaxCount: number;
}

+ 4
- 0
software/Frontend/src/app/models/position.model.ts View File

@@ -0,0 +1,4 @@
export interface Position {
Position: string,
Timestamp: string
}

+ 4
- 0
software/Frontend/src/app/models/robotBattery.model.ts View File

@@ -0,0 +1,4 @@
export interface RobotBattery {
Battery: number;
Timestamp: string;
}

+ 0
- 0
software/Frontend/src/assets/.gitkeep View File


BIN
software/Frontend/src/assets/images/Icons-Card/Timestamp_icon.png View File


BIN
software/Frontend/src/assets/images/Icons-Card/air_humidity_icon.png View File


BIN
software/Frontend/src/assets/images/Icons-Card/air_temperature_icon.png View File


BIN
software/Frontend/src/assets/images/Icons-Card/brightness_icon.png View File


BIN
software/Frontend/src/assets/images/Icons-Card/id_icon.png View File


BIN
software/Frontend/src/assets/images/Icons-Card/name_icon.png View File


BIN
software/Frontend/src/assets/images/Icons-Card/soil_moisture_icon.png View File


BIN
software/Frontend/src/assets/images/background.jpg View File


BIN
software/Frontend/src/assets/images/gruppe1.jpg View File


BIN
software/Frontend/src/assets/images/gruppe2.jpg View File


BIN
software/Frontend/src/assets/images/logo.png View File


+ 35
- 0
software/Frontend/src/custom-theme.scss View File

@@ -0,0 +1,35 @@

// Custom Theming for Angular Material
// For more information: https://material.angular.io/guide/theming
@use '@angular/material' as mat;
// Plus imports for other components in your app.

// Include the common styles for Angular Material. We include this here so that you only
// have to load a single css file for Angular Material in your app.
// Be sure that you only ever include this mixin once!
@include mat.core();

// Define the palettes for your theme using the Material Design palettes available in palette.scss
// (imported above). For each palette, you can optionally specify a default, lighter, and darker
// hue. Available color palettes: https://material.io/design/color/
$Frontend-primary: mat.define-palette(mat.$indigo-palette);
$Frontend-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);

// The warn palette is optional (defaults to red).
$Frontend-warn: mat.define-palette(mat.$red-palette);

// Create the theme object. A theme consists of configurations for individual
// theming systems such as "color" or "typography".
$Frontend-theme: mat.define-light-theme((
color: (
primary: $Frontend-primary,
accent: $Frontend-accent,
warn: $Frontend-warn,
)
));

// Include theme styles for core and each component used in your app.
// Alternatively, you can import and @include the theme mixins for each component
// that you are using.
@include mat.all-component-themes($Frontend-theme);


+ 3
- 0
software/Frontend/src/environments/environment.prod.ts View File

@@ -0,0 +1,3 @@
export const environment = {
production: true
};

+ 16
- 0
software/Frontend/src/environments/environment.ts View File

@@ -0,0 +1,16 @@
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.

export const environment = {
production: false
};

/*
* For easier debugging in development mode, you can import the following file
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
*
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/plugins/zone-error'; // Included with Angular CLI.

BIN
software/Frontend/src/favicon.ico View File


+ 19
- 0
software/Frontend/src/index.html View File

@@ -0,0 +1,19 @@
<!doctype html>
<html lang="en">

<head>
<meta charset="utf-8">
<title>Frontend</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>

<body class="mat-typography">
<app-root></app-root>
</body>

</html>

+ 12
- 0
software/Frontend/src/main.ts View File

@@ -0,0 +1,12 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

if (environment.production) {
enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));

+ 53
- 0
software/Frontend/src/polyfills.ts View File

@@ -0,0 +1,53 @@
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes recent versions of Safari, Chrome (including
* Opera), Edge on the desktop, and iOS and Chrome on mobile.
*
* Learn more in https://angular.io/guide/browser-support
*/

/***************************************************************************************************
* BROWSER POLYFILLS
*/

/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
* because those flags need to be set before `zone.js` being loaded, and webpack
* will put import in the top of bundle, so user need to create a separate file
* in this directory (for example: zone-flags.ts), and put the following flags
* into that file, and then add the following code before importing zone.js.
* import './zone-flags';
*
* The flags allowed in zone-flags.ts are listed here.
*
* The following flags will work for all browsers.
*
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*
* (window as any).__Zone_enable_cross_context_check = true;
*
*/

/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js'; // Included with Angular CLI.


/***************************************************************************************************
* APPLICATION IMPORTS
*/

+ 4
- 0
software/Frontend/src/styles.css View File

@@ -0,0 +1,4 @@
/* You can add global styles to this file, and also import other style files */

html, body { height: 100%; }
body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }

+ 26
- 0
software/Frontend/src/test.ts View File

@@ -0,0 +1,26 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files

import 'zone.js/testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';

declare const require: {
context(path: string, deep?: boolean, filter?: RegExp): {
<T>(id: string): T;
keys(): string[];
};
};

// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting(),
);

// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().forEach(context);

+ 15
- 0
software/Frontend/tsconfig.app.json View File

@@ -0,0 +1,15 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": [
"src/main.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.d.ts"
]
}

+ 0
- 0
software/Frontend/tsconfig.json View File


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save