# 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 |
# 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 |
# 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 |
# 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. |
{ | |||||
"$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": [] | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} |
// 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 | |||||
}); | |||||
}; |
{ | |||||
"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" | |||||
} |
Subproject commit a13b150dfe70b0beb1484f051ad4d7076ba7fd62 |
#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; | |||||
} |
<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> |
// 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(); | |||||
// }); | |||||
// }); |
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) { | |||||
} | |||||
} |
.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; | |||||
} |
<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> |
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(''); | |||||
}); | |||||
}); | |||||
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 = ''; | |||||
} | |||||
} |
.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); | |||||
} |
<div class="container"> | |||||
<div class="outputIcon"> | |||||
<mat-icon>volume_up</mat-icon> | |||||
</div> | |||||
<div class="outputText"> | |||||
<div>{{outputText}}</div> | |||||
</div> | |||||
</div> |
// 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(); | |||||
// }); | |||||
// }); |
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(); | |||||
} | |||||
} |
// 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(); | |||||
// }); | |||||
// }); |
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; | |||||
} | |||||
} |
div { | |||||
height: 70%; | |||||
width: 80%; | |||||
margin: 2% 9%; | |||||
} |
<div #mapContainer></div> |
// 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(); | |||||
// }); | |||||
// }); |
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(); | |||||
} | |||||
} |
.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; | |||||
} |
<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> |
// 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(); | |||||
// }); | |||||
// }); |
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', | |||||
}); | |||||
} | |||||
} |
.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; | |||||
} |
<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> |
// 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(); | |||||
// }); | |||||
// }); |
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)); | |||||
} | |||||
} |
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; | |||||
} |
<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> |
// 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(); | |||||
// }); | |||||
// }); |
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`; | |||||
} | |||||
} | |||||
} |
// 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(); | |||||
// }); | |||||
// }); |
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(''); | |||||
} | |||||
} |
// 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(); | |||||
// }); | |||||
// }); |
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); | |||||
} | |||||
} |
// 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(); | |||||
// }); | |||||
// }); |
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); | |||||
} | |||||
} |
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(); | |||||
}); | |||||
}); |
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; | |||||
} | |||||
} |
import { NgModule } from '@angular/core'; | |||||
import { RouterModule, Routes } from '@angular/router'; | |||||
const routes: Routes = []; | |||||
@NgModule({ | |||||
imports: [RouterModule.forRoot(routes)], | |||||
exports: [RouterModule] | |||||
}) | |||||
export class AppRoutingModule { } |
.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; | |||||
} | |||||
} |
<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> |
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'); | |||||
}); | |||||
}); |
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; | |||||
} | |||||
} | |||||
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 { } |
.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; | |||||
} |
<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> |
// 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(); | |||||
// // }); | |||||
// }); |
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; | |||||
} | |||||
} | |||||
} |
.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; | |||||
} |
<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> |
// 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(); | |||||
// }); | |||||
// }); |
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! | |||||
} | |||||
} |
:host ::ng-deep { | |||||
.mat-dialog-title { | |||||
color: red; | |||||
} | |||||
.mat-button { | |||||
background-color: ghostwhite; | |||||
} | |||||
} |
<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> |
// 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(); | |||||
// }); | |||||
// }); |
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); | |||||
} | |||||
} |
:host ::ng-deep { | |||||
.mat-dialog-title { | |||||
color: red; | |||||
} | |||||
.mat-button { | |||||
background-color: ghostwhite; | |||||
} | |||||
} |
<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> |
// 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(); | |||||
// }); | |||||
// }); |
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(); | |||||
} | |||||
} |
.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; | |||||
} |
<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> |
// 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(); | |||||
// }); | |||||
// }); |
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 { | |||||
} | |||||
} |
export interface Plant { | |||||
AirTemperature: number; | |||||
AirHumidity: number; | |||||
SoilMoisture: number; | |||||
Brightness: number; | |||||
PlantID: number; | |||||
Timestamp: string; | |||||
MeasurementID: string; | |||||
PlantName: string; | |||||
} |
export interface PlantCount { | |||||
CurrentCount: number; | |||||
MaxCount: number; | |||||
} |
export interface Position { | |||||
Position: string, | |||||
Timestamp: string | |||||
} |
export interface RobotBattery { | |||||
Battery: number; | |||||
Timestamp: string; | |||||
} |
// 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); | |||||
export const environment = { | |||||
production: true | |||||
}; |
// 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. |
<!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> |
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)); |
/** | |||||
* 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 | |||||
*/ |
/* 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; } |
// 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); |
/* 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" | |||||
] | |||||
} |