Compare commits

..
101 Commits
Author SHA1 Message Date
caliskanbi 556736ccf5 Class Diagram Updated 2023-09-26 21:57:42 +02:00
duelgerke82357 410e27adc3 Final final Status 2023-09-20 18:39:43 +02:00
duelgerke82357 f6f6421620 Final status 2023-09-20 18:26:22 +02:00
waldluis 5e14633f42 Robot UML update 2023-08-31 10:23:02 +02:00
waldluis 1778a211bb Raspy comment update, Robot UML update 2023-08-17 10:37:00 +02:00
caliskan 22bcf7310f backend code fully commented 2023-08-15 23:22:26 +02:00
caliskan 90235a73f1 [add] fixed backend data 2023-08-14 20:52:07 +02:00
waldluis 2c065573a5 Robot docu software description added 2023-07-24 15:45:34 +02:00
waldluis 2a3136503a Robot docu extended 2023-06-28 15:05:47 +02:00
waldluis d9ad3bce0c Robot comments extended, Docu updated 2023-06-09 10:08:27 +02:00
duelgerke82357 e975585f57 Merge branch 'master' of https://git.efi.th-nuernberg.de/gitea/caliskanbi83990/projektarbeit_duelger_waldhauser_caliskan 2023-06-08 20:07:57 +02:00
duelgerke82357 9b1bcbebde Kommentiert 2023-06-08 20:07:27 +02:00
waldluis a857f870a5 Robot Docu drive programms added 2023-06-08 17:15:58 +02:00
waldhauserlu78556 847a424049 Robot changes after Test 2023-06-08 16:40:54 +02:00
duelgerke82357 d06f0a46e7 Ordner deleted 2023-06-08 10:48:33 +02:00
duelgerke82357 9166d4040e Erster und wichtigster Push :D 2023-06-08 10:44:54 +02:00
caliskanbi a13b150dfe DRIVE ALL ADDED 2023-06-08 01:42:43 +02:00
waldhauserlu78556 445373a668 Roboter comments added 2023-06-06 11:28:24 +02:00
waldluis bd1daa0489 Robot Docu problems added 2023-06-04 11:24:23 +02:00
waldhauserlu78556 5e21e4b2c5 Autostart added, EV3 error handling improved 2023-06-04 10:37:24 +02:00
waldhauserlu78556 a368637346 Minor Changes in mainProg, defines synchronized 2023-06-02 13:51:49 +02:00
waldluis 99b1bbb065 Robot Docu updated 2023-05-31 14:22:17 +02:00
waldhauserlu78556 a8f365cd4f Changed readSensors for better ErrorHandling 2023-05-30 11:03:21 +02:00
caliskan 052ace21bf Changes after Test added 2023-05-29 15:56:17 +02:00
caliskanbi 612e3b1b93 date time fixed 2023-05-25 12:15:05 +02:00
waldhauserlu78556 8fa5cae836 Drive all sensor test and Bugfix 2023-05-24 15:27:14 +02:00
waldhauserlu78556 4cadbf9c29 Drive all Test and bugfix 2023-05-23 18:08:41 +02:00
waldluis 409c38a96d Drive all Plants added 2023-05-17 21:35:58 +02:00
waldluis 6964f9855e Docu update sensors and drive all programm 2023-05-16 10:55:12 +02:00
waldhauserlu78556 a0c7bc9077 Bug fixes, drive_all started 2023-05-15 11:18:48 +02:00
caliskanbi b70d445480 defines updated 2023-05-15 11:17:10 +02:00
caliskanbi 93b4075c35 requirements fixed 2023-05-15 10:52:18 +02:00
caliskanbi 4e60cd9ad0 Error Handling BackEnd added, logging included, order list and robot class added 2023-05-15 10:50:19 +02:00
waldhauserlu78556 062d08fe1a Bugfixes after drive test 2023-05-14 10:44:33 +02:00
waldhauserlu78556 7452d6aae8 SoilMoisture, Camera (and GPS) added 2023-05-13 15:19:27 +02:00
waldhauserlu78556 43fbbfcbbb RobotReady and SendError added 2023-05-12 16:51:31 +02:00
waldhauserlu78556 0385aa7792 Robot logging added 2023-05-12 13:56:56 +02:00
caliskan c35028f354 FIXED Backend functions 2023-05-10 21:58:11 +02:00
caliskanbi a16c313a6c TEST+ 2023-05-10 16:16:14 +02:00
caliskanbi 35118e8c20 NEW FILES ADDED 2023-05-10 16:04:19 +02:00
waldhauserlu78556 b8cb4f6217 Minor changes 2023-05-10 15:58:27 +02:00
waldluis 48a4bbe710 Docu for sensors updated 2023-05-09 10:24:15 +02:00
caliskanbi 0dc80b8981 new topics added 2023-05-08 19:06:53 +02:00
waldluis 1502da2b11 Docu for robot updated 2023-05-08 11:41:47 +02:00
waldhauserlu78556 9f12f21ac4 Sensor error handling added 2023-05-07 12:32:50 +02:00
caliskan 56f9065fc7 database fixed and added 2023-05-06 20:52:18 +02:00
waldhauserlu78556 a6259e5392 SHH error handling, sensors class changed to functions 2023-05-06 12:17:29 +02:00
waldhauserlu78556 20bad535eb EV3 battery status added 2023-05-05 14:22:12 +02:00
waldluis 830d662e08 Added color code driving, changed Plant order, Docu update 2023-05-05 10:40:33 +02:00
waldluis 0a53fe171a StarUML test 2023-05-03 13:25:01 +02:00
waldluis 9832c4e986 Modelio test 2023-05-03 12:51:37 +02:00
waldluis d3ee161444 Architecture added, spelling fixes 2023-05-02 14:08:07 +02:00
waldhauserlu78556 1e4fb4c4b4 Sensor Bug Fixes 2023-04-30 12:12:11 +02:00
waldhauserlu78556 ae1e67f204 Functions excluded out of mainProg, added defines.py to raspy 2023-04-28 11:17:04 +02:00
caliskan 58bdd595d5 Order Handler added 2023-04-27 19:00:09 +02:00
waldhauserlu78556 24268d3039 Minor changes to start without sensors 2023-04-27 17:33:24 +02:00
caliskanbi 5338490503 some additions 2023-04-27 08:53:21 +02:00
waldluis b798e138fa Added PythonDoc and simplified drive thread 2023-04-24 15:48:53 +02:00
waldluis 0c08ab1d8b MQTT messages defined 2023-04-24 13:04:03 +02:00
caliskanbi 33e2a058d3 Merge branch 'master' of https://git.efi.th-nuernberg.de/gitea/caliskanbi83990/projektarbeit_duelger_waldhauser_caliskan 2023-04-24 11:58:46 +02:00
caliskanbi cd07f728ee definitions word file changed 2023-04-24 11:58:33 +02:00
caliskanbi83990 c4ef41de90 ~ file deleted 2023-04-24 09:57:08 +00:00
caliskanbi 8efbe4fc71 defines in upper case and some mqtt discussion 2023-04-24 11:55:10 +02:00
caliskanbi f3fab66cb5 defines changed to software dir and topics added & callback messages added in backend 2023-04-24 11:21:01 +02:00
waldluis d54bfdd101 EV3 Scripts, and Docu update robot driving 2023-04-22 14:59:02 +02:00
waldhauserlu78556 307e9fcb7c Bugfixes and first drive test 2023-04-22 14:34:46 +02:00
waldhauserlu78556 6adc5a3426 SSH start for robot scripts 2023-04-22 12:32:27 +02:00
caliskanbi 57cadbbbf0 BackEnd files changed 2023-04-20 03:21:08 +02:00
waldluis 5c02b0a2bc UML PDF added 2023-04-19 12:32:16 +02:00
caliskanbi 14fea73572 function layout added 2023-04-19 03:40:51 +02:00
caliskanbi 9396c59e86 new backend files added: main created and plantdatabase files from Makeathon extracted 2023-04-19 02:47:49 +02:00
waldluis f29e567f07 Robot Docu extended and main to mainProg added 2023-04-18 16:50:34 +02:00
waldluis bbc895c7dd Docu update 2023-04-17 12:43:46 +02:00
waldluis 6e66611e06 Docu small changes and MQTT topics added to robot 2023-04-16 11:46:55 +02:00
waldluis 9b32acdc68 MQTT and JSON Definition Docu added 2023-04-13 15:57:46 +02:00
waldluis 3d0bc3c4d9 ev3 First Testfiles 2023-04-07 16:05:15 +02:00
waldhauserlu78556 60be6862b0 Brightness Sensor added, requirements.txt updated 2023-04-04 17:24:44 +02:00
waldhauserlu78556 9388f765f4 DHT22 included 2023-04-04 15:56:13 +02:00
waldluis a3e3da1297 JSON MQTT receive and Sensor-Class added 2023-04-03 14:59:23 +02:00
waldhauserlu78556 d27076ba54 Send JSON via MQTT added 2023-04-03 14:57:56 +02:00
caliskan 7f1af76a55 .vscode folder deleted 2023-04-03 11:27:14 +02:00
caliskanbi83990 3bb6f8c2c7 gitIgnore changed .vscode 2023-04-03 09:18:37 +00:00
waldluis 3f1176917a GitIgnore .vscode 2023-04-03 10:50:30 +02:00
waldluis c93d1c4794 GitIgnore .vscode 2023-04-03 10:49:20 +02:00
waldluis 48f3dc9499 Reset settings.json 2023-04-03 10:36:21 +02:00
waldhauserlu78556 70559870a5 Updated GitIgnore2 2023-04-03 10:32:31 +02:00
waldhauserlu78556 7b9f560da5 Updated GitIgnore 2023-04-03 10:25:46 +02:00
waldluis e39b2a2753 Raspy MQTT Tests added 2023-04-03 10:09:53 +02:00
waldhauserlu78556 fc545d59a1 Raspy test push2 2023-04-01 14:10:40 +02:00
waldhauserlu78556 3a4a8e11a9 Raspy test push 2023-04-01 14:09:14 +02:00
waldluis a941297323 Added Passwords 2023-04-01 10:50:56 +02:00
waldluis f93b7d4f55 Recovered GC Raspy files 2023-04-01 09:44:27 +02:00
waldluis 4cfc6442a7 Roboter info file added 2023-03-30 17:29:18 +02:00
waldluis 969524966c test gui push 2023-03-30 17:24:42 +02:00
waldluis f5af78ffe4 Sensor file GC 2023-03-30 17:22:29 +02:00
waldluis 8058012260 Drive file GC 2023-03-30 17:20:41 +02:00
caliskanbi b155fcb6a4 Merge branch 'master' of https://git.efi.th-nuernberg.de/gitea/caliskanbi83990/projektarbeit_duelger_waldhauser_caliskan 2023-03-30 16:02:50 +02:00
caliskanbi 3878020331 pull added 2023-03-30 16:02:45 +02:00
caliskanbi 057dd56e2c commands line by line 2023-03-30 16:01:18 +02:00
waldluis bb09d15a56 Merge branch 'master' of https://git.efi.th-nuernberg.de/gitea/caliskanbi83990/projektarbeit_duelger_waldhauser_caliskan 2023-03-30 16:01:05 +02:00
waldluis 77c3f32f6f Test-Push 2023-03-30 16:00:09 +02:00
174 changed files with 48726 additions and 34 deletions
+5
View File
@@ -114,3 +114,8 @@ dmypy.json
# Pyre type checker
.pyre/
# VSCode
.vscode/
#
~*
+36
View File
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="backend_database.db" uuid="a5aefbe2-75a1-4a5e-ae27-5f87ff08045a">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/software/backend/backend_database.db</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="backend_database" uuid="4f1f9bd8-463b-4b3b-b101-cc4b9b84e2e1">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:C:\Users\bcali\Documents\Uni\BEI6\Projektarbeit\projektarbeit_duelger_waldhauser_caliskan\software\backend\tests\backend_database.db</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
<libraries>
<library>
<url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.40.1/sqlite-jdbc-3.40.1.jar</url>
</library>
</libraries>
</data-source>
<data-source source="LOCAL" name="test_database" uuid="a72361b0-954e-42bd-8930-b14a0b4c1023">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:C:\Users\bcali\Documents\Uni\BEI6\Projektarbeit\projektarbeit_duelger_waldhauser_caliskan\software\backend\tests\test_database.db</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
<libraries>
<library>
<url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.40.1/sqlite-jdbc-3.40.1.jar</url>
</library>
</libraries>
</data-source>
</component>
</project>
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PublishConfigData" autoUpload="Always" serverName="backend" remoteFilesAllowedToDisappearOnAutoupload="false">
<serverData>
<paths name="backend">
<serverdata>
<mappings>
<mapping deploy="/home/lego/SMARTGARDENING" local="$PROJECT_DIR$" web="/" />
</mappings>
</serverdata>
</paths>
</serverData>
<option name="myAutoUpload" value="ALWAYS" />
</component>
</project>
+1 -1
View File
@@ -4,7 +4,7 @@
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.10 (projektarbeit_duelger_waldhauser_caliskan)" jdkType="Python SDK" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="file://$PROJECT_DIR$/software/backend/plantdatabase.py" dialect="GenericSQL" />
<file url="PROJECT" dialect="SQLite" />
</component>
</project>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SshConfigs">
<configs>
<sshConfig authType="PASSWORD" host="lego-K53SV" id="40359a92-2c87-41df-943b-cfac73c7ea3d" port="22" nameFormat="DESCRIPTIVE" username="lego" useOpenSSHConfig="true" />
</configs>
</component>
</project>
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="WebServers">
<option name="servers">
<webServer id="1b3e9e3f-7394-4cbc-a32d-b95ef2abda52" name="backend">
<fileTransfer accessType="SFTP" host="lego-K53SV" port="22" sshConfigId="40359a92-2c87-41df-943b-cfac73c7ea3d" sshConfig="lego@lego-K53SV:22 password">
<advancedOptions>
<advancedOptions dataProtectionLevel="Private" keepAliveTimeout="0" passiveMode="true" shareSSLContext="true" />
</advancedOptions>
</fileTransfer>
</webServer>
</option>
</component>
</project>
Binary file not shown.
@@ -0,0 +1,3 @@
__pycache__/
*.pyc
.venv/
@@ -0,0 +1,13 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
"lego-education.ev3-micropython"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": [
"ms-python.python"
]
}
@@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Download and Run",
"type": "ev3devBrowser",
"request": "launch",
"program": "/home/robot/${workspaceRootFolderName}/main.py",
"interactiveTerminal": false
}
]
}
@@ -0,0 +1,7 @@
// Place your settings in this file to overwrite default and user settings.
{
"files.eol": "\n",
"debug.openDebug": "neverOpen",
"python.linting.enabled": false,
"python.languageServer": "None"
}
@@ -0,0 +1,14 @@
# Import the necessary modules from the ev3dev2 library
from ev3dev2.motor import LargeMotor, OUTPUT_A, OUTPUT_D
from time import sleep
# Connect the motors to the EV3 brick
motor_left = LargeMotor(OUTPUT_A)
motor_right = LargeMotor(OUTPUT_D)
# Move the robot forward for 5 seconds
motor_left.on(50)
motor_right.on(50)
sleep(5)
motor_left.off()
motor_right.off()
@@ -0,0 +1,3 @@
__pycache__/
*.pyc
.venv/
@@ -0,0 +1,13 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
"lego-education.ev3-micropython"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": [
"ms-python.python"
]
}
@@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Download and Run",
"type": "ev3devBrowser",
"request": "launch",
"program": "/home/robot/${workspaceRootFolderName}/main.py",
"interactiveTerminal": false
}
]
}
@@ -0,0 +1,7 @@
// Place your settings in this file to overwrite default and user settings.
{
"files.eol": "\n",
"debug.openDebug": "neverOpen",
"python.linting.enabled": false,
"python.languageServer": "None"
}
@@ -0,0 +1,65 @@
#!/usr/bin/env pybricks-micropython
from pybricks.hubs import EV3Brick
from pybricks.ev3devices import (Motor, TouchSensor, ColorSensor,
InfraredSensor, UltrasonicSensor, GyroSensor)
from pybricks.parameters import Port, Stop, Direction, Button, Color
from pybricks.tools import wait, StopWatch, DataLog
from pybricks.robotics import DriveBase
from pybricks.media.ev3dev import SoundFile, ImageFile
import sys
def fun_1(ev3):
pass
def fun_2(a,b):
return 1
def move_1(mo_forward_1, mo_forward_2, mo_le_ri, mo_up_down):
wait(2000)
#move forward
mo_forward_1.run_target(500, 1250,wait=False)
mo_forward_2.run_target(500, 1250)
#move arm
mo_le_ri.run_target(200,-50)
#move sensors
mo_up_down.run_target(500,2500)
wait(2000)
mo_up_down.run_target(500,0)
#move arm
mo_le_ri.run_target(200,0)
#move backwards
mo_forward_1.run_target(500, 0,wait=False)
mo_forward_2.run_target(500, 0)
def move_2():
pass
def move_3():
pass
ev3 = EV3Brick()
motor_forward_1 = Motor(Port.A)
motor_forward_2 = Motor(Port.D)
motor_le_ri = Motor(Port.B, Direction.COUNTERCLOCKWISE, [8, 56]) #with gear for angle
motor_up_down = Motor(Port.C)
#n = int(sys.argv[1])
#if n == 1:
move_1(motor_forward_1,motor_forward_2,motor_le_ri,motor_up_down)
@@ -0,0 +1,55 @@
from flask import Flask
import time
from sensors import readSensors
app = Flask(__name__)
@app.route("/", methods=['GET', 'POST'])
def hello_world():
return "<p>Hello, World!</p>"
"""
SENSOR TEST FUNCTION
"""
@app.route("/measurement_test", methods=['GET'])
def measurement_test():
return readSensors()
"""
"""
@app.route("/temp/<int:sensor_id>", methods=['GET'])
def get_temperature(sensor_id):
if sensor_id == 1:
# here we need to read our sensor values
return "23.4"
elif sensor_id == 2:
return "21.0"
else:
return "not found"
@app.route("/move/<position>", methods=['POST'])
def post_move(position):
if position == 'plant1':
return f"I moved to position: {position}"
# control motors or whatever you like
return f"Somewhere moved, no idea where to: {position}"
@app.route("/all", methods=['GET'])
def get_all():
# read sensor data
# TODO
time.sleep(10)
# put sensor data into a dict
ret = {
"plant_id": 1,
"sensordata_temp":20.5,
"sensordata_humidity": 23.5
}
# return it as json data
return ret
app.run(host='192.168.0.106', port=5000)
@@ -0,0 +1,14 @@
# Import the necessary modules from the ev3dev2 library
from ev3dev2.motor import LargeMotor, OUTPUT_B, OUTPUT_C
from time import sleep
# Connect the motors to the EV3 brick
motor_left = LargeMotor(OUTPUT_B)
motor_right = LargeMotor(OUTPUT_C)
# Move the robot forward for 5 seconds
motor_left.on(50)
motor_right.on(50)
sleep(5)
motor_left.off()
motor_right.off()
@@ -0,0 +1,89 @@
from picamera import PiCamera
from time import sleep
from datetime import datetime
import RPi.GPIO as GPIO
import board
import adafruit_dht
import smbus
import time
#Connect Sensors
#Camera
#camera = PiCamera()
#Air Temp&Humidity
dhtDevice = adafruit_dht.DHT22(board.D4, use_pulseio=False)
#Earth Humidity
chanel = 21
GPIO.setmode(GPIO.BCM)
GPIO.setup(chanel, GPIO.IN)
# Brightness Sensor
bus = smbus.SMBus(1)
bus.write_byte_data(0x39, 0x00 | 0x80, 0x03)
bus.write_byte_data(0x39, 0x01 | 0x80, 0x02)
def readSensors():
#Connect Sensors
#Camera
#camera = PiCamera()
#Air Temp&Humidity
dhtDevice = adafruit_dht.DHT22(board.D4, use_pulseio=False)
#Earth Humidity
chanel = 21
GPIO.setmode(GPIO.BCM)
GPIO.setup(chanel, GPIO.IN)
#Camera
#camera.start_preview()
dt = datetime.now()
ts = datetime.timestamp(dt)
#name = "/home/pi/Pictures/{}.jpg".format(ts)
#camera.capture(name)
print("Camera on")
#camera.stop_preview()
#Air Temp&Humidity
temperature_c = dhtDevice.temperature
humidity = dhtDevice.humidity
print("Temp: {:.1f} C Humidity: {}% ".format(temperature_c, humidity))
#Earth Humidit:
ground_humidity = GPIO.input(chanel)
print("Earth Humitiy: {}".format(ground_humidity))
#Brightness Sensor
# Read data back from 0x0C(12) with command register, 0x80(128), 2 bytes
# ch0 LSB, ch0 MSB
data = bus.read_i2c_block_data(0x39, 0x0C | 0x80, 2)
# Read data back from 0x0E(14) with command register, 0x80(128), 2 bytes
# ch1 LSB, ch1 MSB
data1 = bus.read_i2c_block_data(0x39, 0x0E | 0x80, 2)
# Convert the data
ch0 = data[1] * 256 + data[0]
ch1 = data1[1] * 256 + data1[0]
light_intesity = ch0-ch1
# Output data to screen
print("Visible Value :%d lux" %(ch0 - ch1))
return {"Air_Temperature": temperature_c,
"Air_Humidity": humidity,
"Ground Humidity": ground_humidity,
"Light_Intensity": light_intesity
}
def main():
test = readSensors()
if __name__ == "__main__":
main()
@@ -0,0 +1,14 @@
from picamera import PiCamera
from time import sleep
from datetime import datetime
camera = PiCamera()
camera.start_preview()
dt = datetime.now()
ts = datetime.timestamp(dt)
name = "/home/pi/Pictures/{}.jpg".format(ts)
camera.capture(name)
camera.stop_preview()
@@ -0,0 +1,29 @@
import time
import board
import adafruit_dht
# connect Device
dhtDevice = adafruit_dht.DHT22(board.D4, use_pulseio=False)
# you can pass DHT22 use_pulseio=False if you wouldn't like to use pulseio.
# This may be necessary on a Linux single board computer like the Raspberry Pi,
# but it will not work in CircuitPython.
# dhtDevice = adafruit_dht.DHT22(board.D18, use_pulseio=False)
while True:
try:
# Print the values to the serial port
temperature_c = dhtDevice.temperature
humidity = dhtDevice.humidity
print("Temp: {:.1f} C Humidity: {}% ".format(temperature_c, humidity))
except RuntimeError as error:
# Errors happen fairly often, DHT's are hard to read, just keep going
print(error.args[0])
time.sleep(2.0)
continue
except Exception as error:
dhtDevice.exit()
raise error
time.sleep(2.0)
@@ -0,0 +1,19 @@
import RPi.GPIO as GPIO
import time
chanel = 21
GPIO.setmode(GPIO.BCM)
GPIO.setup(chanel, GPIO.IN)
def callback(chanel):
if GPIO.input(chanel):
print("No water detected")
else:
print("Water detected")
print(GPIO.input(chanel))
GPIO.add_event_detect(chanel, GPIO.BOTH, bouncetime=300)
GPIO.add_event_callback(chanel, callback)
callback(chanel)
@@ -0,0 +1,33 @@
import smbus
import time
# Get I2C bus
bus = smbus.SMBus(1)
# TSL2561 address, 0x39(57)
# Select control register, 0x00(00) with command register, 0x80(128)
# 0x03(03) Power ON mode
bus.write_byte_data(0x39, 0x00 | 0x80, 0x03)
# TSL2561 address, 0x39(57)
# Select timing register, 0x01(01) with command register, 0x80(128)
# 0x02(02) Nominal integration time = 402ms
bus.write_byte_data(0x39, 0x01 | 0x80, 0x02)
time.sleep(0.5)
# Read data back from 0x0C(12) with command register, 0x80(128), 2 bytes
# ch0 LSB, ch0 MSB
data = bus.read_i2c_block_data(0x39, 0x0C | 0x80, 2)
# Read data back from 0x0E(14) with command register, 0x80(128), 2 bytes
# ch1 LSB, ch1 MSB
data1 = bus.read_i2c_block_data(0x39, 0x0E | 0x80, 2)
# Convert the data
ch0 = data[1] * 256 + data[0]
ch1 = data1[1] * 256 + data1[0]
# Output data to screen
print("Full Spectrum(IR + Visible) :%d lux" %ch0)
print("Infrared Value :%d lux" %ch1)
print("Visible Value :%d lux" %(ch0 - ch1))
+21
View File
@@ -0,0 +1,21 @@
from pybricks.hubs import EV3Brick
from pybricks.ev3devices import Motor
from pybricks.parameters import Port
#from ev3dev2.motor import LargeMotor, OUTPUT_A, OUTPUT_B, SpeedPercent, MoveTank
# Initialize the EV3 brick.
ev3 = EV3Brick()
# Initialize a motor at port B.
test_motor = Motor(Port.B)
#test = ev3dev2.motor.LargeMotor('/dev/ttyAMA0')
#test.on(500)
# Run the motor up to 500 degrees per second, for 2 seconds.
test_motor.run_time(500, 2000)
# Stop the motor.
test_motor.stop()
# Beep to indicate that the program has finished.
ev3.speaker.beep()
@@ -0,0 +1,132 @@
Adafruit-Blinka==8.15.2
adafruit-circuitpython-busdevice==5.2.3
adafruit-circuitpython-dht==3.7.8
adafruit-circuitpython-requests==1.13.0
adafruit-circuitpython-tsl2561==3.3.15
adafruit-circuitpython-typing==1.8.3
Adafruit-GPIO==1.0.3
Adafruit-PlatformDetect==3.40.3
Adafruit-PureIO==1.1.10
altgraph==0.17.3
arandr==0.1.10
astroid==2.5.1
asttokens==2.0.4
automationhat==0.2.0
beautifulsoup4==4.9.3
blinker==1.4
blinkt==0.1.2
buttonshim==0.0.2
Cap1xxx==0.1.3
certifi==2022.12.7
cffi==1.15.1
chardet==4.0.0
charset-normalizer==3.0.1
click==8.1.3
colorama==0.4.4
colorzero==1.1
cryptography==3.3.2
cupshelpers==1.0
dbus-python==1.2.16
distro==1.5.0
docutils==0.16
drumhat==0.1.0
envirophat==1.0.0
ExplorerHAT==0.4.2
Flask==2.2.3
fourletterphat==0.1.0
gpiozero==1.6.2
html5lib==1.1
idna==3.4
importlib-metadata==6.0.0
isort==5.6.4
itsdangerous==2.1.2
jedi==0.18.0
Jinja2==3.1.2
lazy-object-proxy==0.0.0
logilab-common==1.8.1
lxml==4.6.3
MarkupSafe==2.1.2
mccabe==0.6.1
microdotphat==0.2.1
mote==0.0.4
motephat==0.0.3
mypy==0.812
mypy-extensions==0.4.3
numpy==1.19.5
oauthlib==3.1.0
pantilthat==0.0.7
parso==0.8.1
pexpect==4.8.0
pgzero==1.2
phatbeat==0.1.1
pianohat==0.1.0
picamera==1.13
picamera2==0.3.8
pidng==4.0.9
piexif==1.1.3
piglow==1.2.5
pigpio==1.78
Pillow==8.1.2
psutil==5.8.0
pybricks==3.2.0
pycairo==1.16.2
pycparser==2.21
pycups==2.0.1
pyftdi==0.54.0
pygame==1.9.6
Pygments==2.7.1
PyGObject==3.38.0
pyinotify==0.9.6
pyinstaller==5.8.0
pyinstaller-hooks-contrib==2023.0
PyJWT==1.7.1
pylint==2.7.2
PyOpenGL==3.1.5
pyOpenSSL==20.0.1
PyQt5==5.15.2
PyQt5-sip==12.8.1
pyserial==3.5b0
pysmbc==1.0.23
python-apt==2.2.1
python-ev3dev==1.2.0
python-ev3dev2==2.1.0.post1
python-prctl==1.7
pyusb==1.2.1
rainbowhat==0.1.0
reportlab==3.5.59
requests==2.28.2
requests-oauthlib==1.0.0
responses==0.12.1
roman==2.0.0
rpi-ws281x==4.3.4
RPi.GPIO==0.7.0
RTIMULib==7.2.1
scrollphat==0.0.7
scrollphathd==1.2.1
Send2Trash==1.6.0b1
sense-hat==2.4.0
simplejpeg==1.6.4
simplejson==3.17.2
six==1.16.0
skywriter==0.0.7
smbus==1.1.post2
smbus-cffi==0.5.1
sn3218==1.2.7
soupsieve==2.2.1
spidev==3.5
ssh-import-id==5.10
sysv-ipc==1.1.0
thonny==4.0.1
toml==0.10.1
touchphat==0.0.1
tsl2561==3.4.0
twython==3.8.2
typed-ast==1.4.2
typing_extensions==4.5.0
unicornhathd==0.0.4
urllib3==1.26.14
v4l2-python3==0.3.1
webencodings==0.5.1
Werkzeug==2.2.3
wrapt==1.12.1
zipp==3.15.0
@@ -0,0 +1,2 @@
python 3.8.2
python 2.7.18
@@ -0,0 +1,11 @@
certifi==2022.12.7
charset-normalizer==3.0.1
click==8.1.3
Flask==2.2.3
idna==3.4
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.2
requests==2.28.2
urllib3==1.26.14
adafruit-circuitpython-dht==3.7.8
+1
View File
@@ -0,0 +1 @@
print("Hello World")
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
+26 -1
View File
@@ -1,11 +1,36 @@
# Adafruit-Blinka==8.16.1 # Raspy only, not possible on other devices -> commented out
# adafruit-circuitpython-busdevice==5.2.4
# adafruit-circuitpython-dht==3.7.8
# adafruit-circuitpython-requests==1.13.1
# adafruit-circuitpython-tsl2561==3.3.15
# adafruit-circuitpython-typing==1.9.0
# Adafruit-PlatformDetect==3.43.0
# Adafruit-PureIO==1.1.10
certifi==2022.12.7
charset-normalizer==3.0.1
click==8.1.3
Flask==2.2.3
idna==3.4
importlib-metadata==6.1.0
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.2
paho-mqtt==1.6.1
# pkg_resources==0.0.0
# pyftdi==0.54.0
# pyserial==3.5
# pyusb==1.2.1
requests==2.28.2
# rpi-ws281x==4.3.4
# RPi.GPIO==0.7.1
# sysv-ipc==1.1.0
typing_extensions==4.5.0
urllib3==1.26.14
paho-mqtt
Werkzeug==2.2.3
zipp==3.15.0
python-ev3dev2==2.1.0.post1
pytest==7.3.1
pip==23.1.2
# spidev==3.6
# picamera==1.13
numpy==1.19.5
+16
View File
@@ -0,0 +1,16 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# For the full list of supported browsers by the Angular framework, please see:
# https://angular.io/guide/browser-support
# You can see what browsers were selected by your queries by running:
# npx browserslist
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions
Firefox ESR
+16
View File
@@ -0,0 +1,16 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md]
max_line_length = off
trim_trailing_whitespace = false
+42
View File
@@ -0,0 +1,42 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
# System files
.DS_Store
Thumbs.db
+27
View File
@@ -0,0 +1,27 @@
# Frontend
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.2.8.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
+105
View File
@@ -0,0 +1,105 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"Frontend": {
"projectType": "application",
"schematics": {},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/frontend",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/custom-theme.scss",
"src/styles.css",
"node_modules/leaflet/dist/leaflet.css"
],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"outputHashing": "all"
},
"development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "Frontend:build:production"
},
"development": {
"browserTarget": "Frontend:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "Frontend:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/custom-theme.scss",
"src/styles.css",
"node_modules/leaflet/dist/leaflet.css"
],
"scripts": []
}
}
}
}
}
}
+44
View File
@@ -0,0 +1,44 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
jasmine: {
// you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321`
},
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
jasmineHtmlReporter: {
suppressAll: true // removes the duplicated traces
},
coverageReporter: {
dir: require('path').join(__dirname, './coverage/frontend'),
subdir: '.',
reporters: [
{ type: 'html' },
{ type: 'text-summary' }
]
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};
+21168
View File
File diff suppressed because it is too large Load Diff
+50
View File
@@ -0,0 +1,50 @@
{
"name": "frontend",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"private": true,
"dependencies": {
"@angular/animations": "^14.2.0",
"@angular/cdk": "^13.0.0",
"@angular/common": "^14.2.0",
"@angular/compiler": "^14.2.0",
"@angular/core": "^14.2.0",
"@angular/forms": "^14.2.0",
"@angular/material": "^13.0.0",
"@angular/platform-browser": "^14.2.0",
"@angular/platform-browser-dynamic": "^14.2.0",
"@angular/router": "^14.2.0",
"@types/googlemaps": "^3.43.3",
"@types/leaflet": "^1.9.3",
"leaflet": "^1.9.3",
"mqtt": "^4.3.7",
"ngx-mqtt": "^15.0.0-alpha.1",
"rxjs": "~7.5.0",
"tslib": "^2.3.0",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "^14.2.8",
"@angular/cli": "~14.2.8",
"@angular/compiler-cli": "^14.2.0",
"@types/jasmine": "~4.0.0",
"jasmine-core": "~4.3.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.0.0",
"typescript": "~4.7.2"
},
"description": "This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.2.8.",
"main": "karma.conf.js",
"keywords": [],
"author": "",
"license": "ISC"
}
@@ -0,0 +1,55 @@
#robotIsOnTour {
color: red;
font-size: larger;
}
#robotIsReady {
color: green;
font-size: larger;
}
.container {
display: grid;
grid-template-rows: 40% 20% 7%;
width: 100%;
height: 100%;
font-family: Cormorant;
}
.item-input {
grid-row: 1 / 2;
grid-column: 1/-1;
justify-self: center;
align-self: center;
}
.item-box-input {
width: 100%;
}
.item-box-output {
width: 100%;
}
.item-output {
grid-row: 3 / 4;
grid-column: 1/-1;
justify-self: start;
align-self: start;
}
.item-robotText {
grid-row: 2 / 3;
grid-column: 1/-1;
justify-self: center;
align-self: center;
font-size: 1.2rem;
padding: 0.7rem;
border: 0.125rem;
border-style: solid;
border-radius: 1rem;
border-color: silver;
box-shadow: 0.2rem 0.2rem 0.2rem silver;
}
@@ -0,0 +1,16 @@
<div class="container">
<div class="item-box-input">
<div class="item-input">
<app-input></app-input>
</div>
</div>
<div class="item-box-output">
<div class="item-output">
<app-output></app-output>
</div>
</div>
<div class="item-robotText">
<div *ngIf="!(robotIsReady$ | async)" id="robotIsOnTour">Robot is on Tour</div>
<div *ngIf="(robotIsReady$ | async)" id="robotIsReady">Robot is Ready</div>
</div>
</div>
@@ -0,0 +1,23 @@
// import { ComponentFixture, TestBed } from '@angular/core/testing';
// import { HomeComponent } from './home.component';
// describe('HomeComponent', () => {
// let component: HomeComponent;
// let fixture: ComponentFixture<HomeComponent>;
// beforeEach(async () => {
// await TestBed.configureTestingModule({
// declarations: [ HomeComponent ]
// })
// .compileComponents();
// fixture = TestBed.createComponent(HomeComponent);
// component = fixture.componentInstance;
// fixture.detectChanges();
// });
// it('should create', () => {
// expect(component).toBeTruthy();
// });
// });
@@ -0,0 +1,17 @@
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 {
// Aktueller RobotStatus wird subscribed
robotIsReady$: Observable<boolean> = this.storeService.currentRobotReady;
constructor(private storeService: StoreService) {
}
}
@@ -0,0 +1,39 @@
.container {
display: grid;
width: 100%;
height: 100%;
grid-template-rows: 25% 75%;
font-family: Cormorant;
margin-top: 3rem;
}
.inputText {
grid-row: 1 / 2;
justify-self: center;
align-self: center;
font-size: 2rem;
}
.inputMicro {
width: 8.3rem;
height: 100%;
grid-row: 2 / 3;
justify-self: center;
align-self: start;
}
button {
padding: 6.25rem 2.31rem;
border: 0.125rem;
border-style: solid;
border-radius: 10.625rem;
border-color: silver;
box-shadow: 0.2rem 0.2rem 0.2rem silver;
background-color: white;
}
mat-icon {
transform: scale(8);
margin-left: 1rem;
}
@@ -0,0 +1,10 @@
<div class="container">
<div class="inputText">
<span>{{text}}</span>
</div>
<div class="inputMicro">
<button mat-menu-item (click)="startStopService()">
<mat-icon [ngStyle]="{'color': micIsClicked ? 'blue' : 'silver' }">micro</mat-icon>
</button>
</div>
</div>
@@ -0,0 +1,69 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MqttRequestService } from 'src/app/Service/Mqtt/mqtt-request.service';
import { SpeechService } from '../speech.service';
import { InputComponent } from './input.component';
describe('InputComponent', () => {
let component: InputComponent;
let fixture: ComponentFixture<InputComponent>;
let speechService: SpeechService;
let mqttRequestService: MqttRequestService;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [InputComponent],
providers: [SpeechService, MqttRequestService],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(InputComponent);
component = fixture.componentInstance;
speechService = TestBed.inject(SpeechService);
mqttRequestService = TestBed.inject(MqttRequestService);
fixture.detectChanges();
});
it('should create the InputComponent', () => {
expect(component).toBeTruthy();
});
it('should initialize the SpeechService if not already initialized', () => {
spyOn(speechService, 'init');
component.ngOnInit();
expect(speechService.init).toHaveBeenCalled();
});
it('should update the text and publish to INPUT when receiving a message', () => {
const message = 'Test message';
spyOn(mqttRequestService, 'publishToINPUT');
component.ngOnInit();
// component.service.currentApprovalStageMessage.next(message);
expect(component.text).toBe(message);
expect(mqttRequestService.publishToINPUT).toHaveBeenCalledWith(message);
});
it('should stop the service when micIsClicked is true', () => {
spyOn(component.service, 'stop');
component.micIsClicked = true;
component.startStopService();
expect(component.service.stop).toHaveBeenCalled();
});
it('should start the service when micIsClicked is false', () => {
spyOn(component.service, 'start');
component.micIsClicked = false;
component.startStopService();
expect(component.service.start).toHaveBeenCalled();
});
it('should unsubscribe from subscription and call onDestroy', () => {
spyOn(component.subscription, 'unsubscribe');
spyOn(component.service, 'onDestory');
component.ngOnDestroy();
expect(component.subscription.unsubscribe).toHaveBeenCalled();
expect(component.service.onDestory).toHaveBeenCalled();
expect(component.text).toBe('');
});
});
@@ -0,0 +1,58 @@
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 speechService: SpeechService,
private mqttRequestService: MqttRequestService
) {
if (!this.speechService.initSpeechServiceStarted) {
this.speechService.init();
}
}
ngOnInit(): void {
// subscribe auf die eingegebene Spracheingabe, gibt als response einen string
this.subscription = this.speechService.currentApprovalStageMessage.subscribe(msg => {
this.text = msg;
if (this.text !== '') {
this.micIsClicked = false;
this.mqttRequestService.publishToINPUT(this.text); // msg wird hier den Request Service weitergeleitet
}
});
}
/**
* Beim klick auf das Mikrofon, wird die Spracheingabe gestartet. Beim 2ten klick auf das
* Mikrofon wird die Spracheingabe gestoppt
*/
startStopService() {
//
if (this.micIsClicked) {
this.micIsClicked = false;
this.speechService.stop(); // Spracheingabe stoppen
} else {
this.micIsClicked = true;
this.speechService.start(); //Spracheingabe starten
}
}
ngOnDestroy() {
this.subscription.unsubscribe()
this.speechService.onDestory();
this.text = '';
}
}
@@ -0,0 +1,34 @@
.container {
display: grid;
grid-template-columns: 10% 90%;
height: 100%;
width: 100%;
font-family: Cormorant;
border: 0.125rem;
border-style: solid;
border-radius: 1rem;
border-color: silver;
box-shadow: 0.2rem 0.2rem 0.2rem silver;
}
.outputIcon {
grid-column: 1 / 2;
justify-self: start;
align-self: end;
}
.outputText {
grid-column: 2 / 3;
justify-self: start;
align-self: center;
font-size: 2rem;
margin: 0;
}
.mat-icon {
margin-left: 0.5rem;
margin-top: 0.5rem;
transform: scale(1.5);
}
@@ -0,0 +1,8 @@
<div class="container">
<div class="outputIcon">
<mat-icon>volume_up</mat-icon>
</div>
<div class="outputText">
<div>{{outputText}}</div>
</div>
</div>
@@ -0,0 +1,23 @@
// import { ComponentFixture, TestBed } from '@angular/core/testing';
// import { OutputComponent } from './output.component';
// describe('OutputComponent', () => {
// let component: OutputComponent;
// let fixture: ComponentFixture<OutputComponent>;
// beforeEach(async () => {
// await TestBed.configureTestingModule({
// declarations: [ OutputComponent ]
// })
// .compileComponents();
// fixture = TestBed.createComponent(OutputComponent);
// component = fixture.componentInstance;
// fixture.detectChanges();
// });
// it('should create', () => {
// expect(component).toBeTruthy();
// });
// });
@@ -0,0 +1,42 @@
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 {
// subsribe auf "responseMessage", sie wird jedes mal getriggert wenn eine Spracheingabe statt findet
this.subscription = this.mqttRequestService.responseMessage.subscribe(msg => {
setTimeout(() => {
this.outputText = msg;
this.speak(this.outputText);
}, 3000);
});
}
/**
* Methode spricht das übergeben string aus --> Sprachausgabe
* @method speak
* @param string
*/
speak(text: string) {
const utterance = new SpeechSynthesisUtterance(text);
utterance.lang = 'en-US';
speechSynthesis.speak(utterance);
}
ngOnDestroy() {
this.subscription.unsubscribe();
this.mqttRequestService.onDestroy();
}
}
@@ -0,0 +1,16 @@
// import { TestBed } from '@angular/core/testing';
// import { SpeechService } from './speech.service';
// describe('SpeechService', () => {
// let service: SpeechService;
// beforeEach(() => {
// TestBed.configureTestingModule({});
// service = TestBed.inject(SpeechService);
// });
// it('should be created', () => {
// expect(service).toBeTruthy();
// });
// });
@@ -0,0 +1,76 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs-compat';
declare var webkitSpeechRecognition: any;
@Injectable({
providedIn: 'root'
})
export class SpeechService {
/*
* currentApprovalStageMessage wird als observable festgelegt,
* damit man aus anderen Komponenten die änderungen mitbekommt!
*/
private approvalStageMessage = new BehaviorSubject('');
currentApprovalStageMessage = this.approvalStageMessage.asObservable();
initSpeechServiceStarted: boolean = false;
recognition = new webkitSpeechRecognition();
isStoppedSpeechRecog = false;
public text = '';
tempWords: string | undefined;
constructor() {
}
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;
});
}
start() {
this.text = '';
this.isStoppedSpeechRecog = false;
this.recognition.start(); // Start der Sprachaufnahme
this.recognition.addEventListener('end', () => {
this.wordConcat()
this.recognition.stop(); // Ende der Sprachaufnahme
if (!this.isStoppedSpeechRecog) {
this.isStoppedSpeechRecog = true;
this.approvalStageMessage.next(this.text); // String was eingelesen wurde, wird mit der Methode "next(..)" an alle subsriber weitergeleitet!
}
});
}
stop() {
this.recognition.stop();
if (!this.isStoppedSpeechRecog) {
this.isStoppedSpeechRecog = true;
console.log("End speech recognition");
this.approvalStageMessage.next(this.text);
}
}
// addiert die Wörter bei der Sprachaufnahme zusammen
wordConcat() {
this.text = this.text + ' ' + this.tempWords;
this.tempWords = '';
}
onDestory() {
this.text = '';
this.approvalStageMessage.next(this.text);
this.isStoppedSpeechRecog = false;
}
}
@@ -0,0 +1,5 @@
div {
height: 70%;
width: 80%;
margin: 2% 9%;
}
@@ -0,0 +1 @@
<div #mapContainer></div>
@@ -0,0 +1,23 @@
// import { ComponentFixture, TestBed } from '@angular/core/testing';
// import { MapComponent } from './map.component';
// describe('MapComponent', () => {
// let component: MapComponent;
// let fixture: ComponentFixture<MapComponent>;
// beforeEach(async () => {
// await TestBed.configureTestingModule({
// declarations: [MapComponent]
// })
// .compileComponents();
// fixture = TestBed.createComponent(MapComponent);
// component = fixture.componentInstance;
// fixture.detectChanges();
// });
// it('should create', () => {
// expect(component).toBeTruthy();
// });
// });
@@ -0,0 +1,23 @@
import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core';
import * as L from 'leaflet';
import 'leaflet/dist/leaflet.css';
@Component({
selector: 'app-map',
templateUrl: './map.component.html',
styleUrls: ['./map.component.css']
})
export class MapComponent implements AfterViewInit {
@ViewChild('mapContainer', { static: false }) mapContainer: ElementRef;
ngAfterViewInit(): void {
const map = L.map(this.mapContainer.nativeElement).setView([51.505, -0.09], 13);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
}).addTo(map);
L.marker([49.452785, 11.096963]).addTo(map) //Koordinaten [X,Y]
.bindPopup('Robot')
.openPopup();
}
}
@@ -0,0 +1,85 @@
.IMG-Plants {
border-radius: 0.03125rem;
width: 9.25rem;
height: 7.375rem;
margin: 0rem 0.3125rem;
}
.icons-measurements {
width: 1rem;
height: 0.75rem;
margin: 0rem 0.3125rem;
}
.container {
min-width: 40rem;
width: 100%;
min-height: 9rem;
padding: 0.3125rem;
box-shadow: 0.3125rem 0.3125rem 0.3125rem silver;
border-radius: 1.25rem;
border-color: silver;
border-width: 0.0625rem;
border-style: solid;
margin-bottom: 1rem;
font-family: Cormorant;
display: grid;
grid-template-columns: 45% 50% 5%;
justify-content: center;
align-content: center;
font-size: 1.125rem;
}
.item-header-content {
grid-column: 1/2;
justify-self: center;
align-self: center;
display: grid;
width: 100%;
grid-template-columns: 55% 45%;
grid-template-rows: 25% 75%;
justify-content: start;
align-content: start;
}
.item-header {
grid-column: 1/3;
grid-row: 1/2;
justify-self: center;
align-self: start;
font-size: 1.875rem;
margin-bottom: 1rem;
}
.item-Measurements-IconsAndNames {
grid-column: 1/2;
grid-row: 2/3;
justify-self: start;
align-self: center;
}
.item-Measurements-Values {
grid-column: 2/3;
justify-self: start;
align-self: center;
}
.item-IMG {
justify-self: center;
align-self: end;
}
.item-delete {
justify-self: end;
align-self: center;
}
.delete-icon {
transform: scale(1.2);
}
p {
margin: 0;
}
@@ -0,0 +1,38 @@
<div class="container">
<div class="item-header-content">
<div class="item-header">
<p class="card-title">{{plant.PlantName}}</p>
</div>
<div class="item-Measurements-IconsAndNames">
<img class="icons-measurements" src="assets/images/Icons-Card/air_humidity_icon.png" alt="not available">
Air Humidity: <br>
<img class="icons-measurements" src="assets/images/Icons-Card/air_temperature_icon.png" alt="not available">
Air Temperature: <br>
<img class="icons-measurements" src="assets/images/Icons-Card/soil_moisture_icon.png" alt="not available">
Soil Moisture: <br>
<img class="icons-measurements" src="assets/images/Icons-Card/brightness_icon.png" alt="not available">
Brightness:<br>
<img class="icons-measurements" src="assets/images/Icons-Card/Timestamp_icon.png" alt="not available">
Timestamp:
</div>
<div class="item-Measurements-Values">
<p>{{plant.AirHumidity}} %</p>
<p>{{plant.AirTemperature}} °C</p>
<p>{{plant.SoilMoisture}} %</p>
<p>{{plant.Brightness}} Lux</p>
<p>{{getFormattedLastRequestedTimestamp() | async}}</p>
</div>
</div>
<div class="item-IMG">
<img class="IMG-Plants" src="assets/images/Pflanze.png" alt="Plant is not available">
<img class="IMG-Plants" src="assets/images/Pflanze.png" alt="Plant is not available">
</div>
<div class="item-delete">
<button class="item-Configure" mat-icon-button color="primary" (click)="onConfigurePlant()">
<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>
@@ -0,0 +1,23 @@
// import { ComponentFixture, TestBed } from '@angular/core/testing';
// import { PlantCardComponent } from './plant-card.component';
// describe('PlantCardComponent', () => {
// let component: PlantCardComponent;
// let fixture: ComponentFixture<PlantCardComponent>;
// beforeEach(async () => {
// await TestBed.configureTestingModule({
// declarations: [PlantCardComponent]
// })
// .compileComponents();
// fixture = TestBed.createComponent(PlantCardComponent);
// component = fixture.componentInstance;
// fixture.detectChanges();
// });
// it('should create', () => {
// expect(component).toBeTruthy();
// });
// });
@@ -0,0 +1,78 @@
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) {
}
/**
* Methode wird in der HTML datei verwendet damit der Wert nicht undefined ist.
* @method
*/
getFormattedLastRequestedTimestamp(): Observable<string> {
return of(this.calculateTimestamp(this.plant.Timestamp))
}
/**
* Methode dividiert den Timestamp der Pflanze mit der aktuellen Zeit. Die Zeit wird dann in Tagen, Stunden, Minuten, Sekunden angezeigt
* @method
* @return string wie z.B "1 hour ago"
*/
calculateTimestamp(timestamp: string): string {
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`;
}
}
/**
* Methode öffnet den Dialog "DeleteConfirmDialogComponent"
* @method
*/
onDeletePlant() {
this.dialog.open(DeleteConfirmDialogComponent, {
data: this.plant,
width: '400px',
});
}
/**
* Methode öffnet den Dialog "ConfigurePlantDialogComponent"
* @method
*/
onConfigurePlant() {
this.dialog.open(ConfigurePlantDialogComponent, {
data: {
plant: this.plant
},
width: '400px',
});
}
}
@@ -0,0 +1,39 @@
.container {
display: grid;
width: 100%;
grid-template-columns: 50% 50%;
grid-template-rows: 20% 80%;
margin-top: 3.125rem;
}
.buttonSearch {
background-color: white;
border: none;
}
.item-Search {
grid-row: 1 / 2;
grid-column: 1/2;
justify-self: end;
align-self: center;
margin-right: 8rem;
}
.item-NewPlant {
grid-row: 1 / 2;
grid-column: 2/3;
justify-self: start;
align-self: center;
margin-left: 18rem;
}
.item-Cards {
grid-row: 2 / -1;
grid-column: 1/3;
justify-self: center;
align-self: center;
}
mat-spinner {
margin: 7rem auto;
}
@@ -0,0 +1,32 @@
<div class="container" *ngIf="(plants$ |async) !== undefined; else errorLoadingData">
<div class="item-Search">
<form class="example-form">
<mat-form-field class=" example-full-width" appearance="fill">
<input type="text" placeholder="Plant Name" aria-label="Plant Name" matInput [formControl]="myControl"
[matAutocomplete]="auto">
<mat-autocomplete autoActiveFirstOption #auto="matAutocomplete"
(optionSelected)='onOptionSelect($event.option.value)'>
<mat-option *ngFor="let option of filteredOptions | async" [value]="option">
{{option}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
<button class="buttonSearch"> <mat-icon matSuffix>search</mat-icon></button>
</form>
</div>
<div class="example-button-row item-NewPlant">
<button class="buttonNew" mat-raised-button color="primary" (click)="onNewPlant()">New Plant</button>
</div>
<div class="item-Cards">
<div *ngIf="selectedPlant === 'undefined'; else elseBlock">
<app-plant-card *ngFor="let plant of (plants$ | async)" [plant]="plant"></app-plant-card>
</div>
<ng-template #elseBlock>
<app-plant-card [plant]="onSearchPlant(selectedPlant) | async"></app-plant-card>
<button mat-raised-button color="primary" (click)="onOptionDefault()">Show all
Plants</button>
</ng-template>
</div>
</div>
<ng-template #errorLoadingData><mat-spinner></mat-spinner>
</ng-template>
@@ -0,0 +1,23 @@
// import { ComponentFixture, TestBed } from '@angular/core/testing';
// import { PlantsComponent } from './plants.component';
// describe('PlantsComponent', () => {
// let component: PlantsComponent;
// let fixture: ComponentFixture<PlantsComponent>;
// beforeEach(async () => {
// await TestBed.configureTestingModule({
// declarations: [ PlantsComponent ]
// })
// .compileComponents();
// fixture = TestBed.createComponent(PlantsComponent);
// component = fixture.componentInstance;
// fixture.detectChanges();
// });
// it('should create', () => {
// expect(component).toBeTruthy();
// });
// });
@@ -0,0 +1,69 @@
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) {
// sobald es eine änderung bei den pflanzenNamen gibt, wird bei der suchleiste ein neuer filter eingesetzt
this.options$.subscribe(response => {
this.filteredOptions = this.myControl.valueChanges.pipe(
startWith(''),
map(value => this._filter(value || '', response)),
);
})
}
/**
* es wird nach dem Pflanzen Namen gesucht.
* @method
* @return Pflant
*/
onSearchPlant(plantName: string): Observable<Plant> {
return this.storeService.getPlant(plantName);
}
/**
* Methode öffnet das Dialog "AddPlantDialogComponent"
* @method
*/
onNewPlant() {
this.dialog.open(AddPlantDialogComponent, {
width: '420px',
});
}
/**
* die ausgewählte Pflanze wird in "selectedPlant" geschrieben
* @method
*/
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));
}
}
@@ -0,0 +1,88 @@
img {
border-radius: 0.5rem;
width: 15.625rem;
height: 12.5rem;
margin: 0rem 0.3125rem;
}
.container {
text-align: center;
display: grid;
grid-template-columns: 40.625rem 12.5rem;
grid-template-rows: 12.5rem 6.25rem 6.25rem;
width: 53.125rem;
min-width: 46.875rem;
font-family: Cormorant;
box-shadow: 0.3125rem 0.3125rem 0.3125rem silver;
border-radius: 1.25rem;
border-color: silver;
border-width: 0.0625rem;
border-style: solid;
padding: 0.625rem;
margin: auto;
margin-top: 3rem;
}
.robotImage {
grid-row: 1 / -1;
grid-column: 2 / 3;
justify-self: end;
align-self: start;
}
.robotAkku {
grid-row: 1 / 2;
grid-column: 1/2;
justify-self: start;
align-self: center;
display: grid;
grid-template-columns: 15.625rem 12.5rem 12.5rem;
align-items: center;
}
.akkuMsg {
grid-column: 1/2;
justify-self: start;
align-self: center;
margin: 0;
}
.akkuProgressbar {
grid-column: 2/3;
justify-self: center;
align-self: center;
}
.akkuValue {
grid-column: 3/4;
justify-self: start;
align-self: center;
margin: 0;
margin-left: 0.3125rem;
}
.robotCoordinates {
grid-row: 2 / 3;
grid-column: 1/2;
justify-self: start;
align-self: center;
display: grid;
grid-template-columns: 15.625rem 12.5rem;
align-items: center;
}
.item-timestamp-name {
grid-column: 1/2;
justify-self: start;
align-self: start;
}
.item-timestamp-value {
grid-column: 2/3;
justify-self: start;
align-self: center;
}
mat-spinner {
margin: 7rem auto;
}
@@ -0,0 +1,18 @@
<div class="container"
*ngIf="robotBatteryResponse$ !== undefined && (robotBatteryResponse$ |async) !== undefined; else errorLoadingData">
<div class="robotImage">
<img src="assets/images/Robot.png" 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>
@@ -0,0 +1,23 @@
// import { ComponentFixture, TestBed } from '@angular/core/testing';
// import { RobotComponent } from './robot.component';
// describe('RobotComponent', () => {
// let component: RobotComponent;
// let fixture: ComponentFixture<RobotComponent>;
// beforeEach(async () => {
// await TestBed.configureTestingModule({
// declarations: [ RobotComponent ]
// })
// .compileComponents();
// fixture = TestBed.createComponent(RobotComponent);
// component = fixture.componentInstance;
// fixture.detectChanges();
// });
// it('should create', () => {
// expect(component).toBeTruthy();
// });
// });
@@ -0,0 +1,66 @@
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) {
}
/**
* Methode setzt die Farbe der LoadingBar auf Rot ("warn") oder Blau ("Primary")
* @method
* @return warn oder primary
*/
getLoadingBarColor(): Observable<ThemePalette> {
return this.batteryChargingStatus$.pipe(
map(chargingStatus => chargingStatus < 20 ? "warn" : "primary")
)
}
/**
* Methode wird in der HTML datei verwendet damit der Wert nicht undefined ist.
* @method
*/
getFormattedLastRequestedBatteryState(): Observable<string> {
return this.requestTimestamp$.pipe(
map(timestamp => this.formatLastRequestedBatteryState(timestamp))
)
}
/**
* Methode dividiert den Timestamp der Pflanze mit der aktuellen Zeit. Die Zeit wird dann in Tagen, Stunden, Minuten, Sekunden angezeigt
* @method
* @return string wie z.B "1 hour ago"
*/
formatLastRequestedBatteryState(timestamp: string): string {
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`;
}
}
}
@@ -0,0 +1,16 @@
// import { TestBed } from '@angular/core/testing';
// import { MqttRequestService } from './mqtt-request.service';
// describe('MqttRequestService', () => {
// let service: MqttRequestService;
// beforeEach(() => {
// TestBed.configureTestingModule({});
// service = TestBed.inject(MqttRequestService);
// });
// it('should be created', () => {
// expect(service).toBeTruthy();
// });
// });
@@ -0,0 +1,193 @@
import { Injectable } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { BehaviorSubject, Subscription } from 'rxjs';
import { take } from 'rxjs/operators';
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 { 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 {
// wird für die Sprachausgabe verwendet
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(errorMsg => {
if (typeof errorMsg !== "object") {
this.dialog.open(ErrorDialogComponent, {
data: errorMsg,
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/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 (data === "False") {
this.mqttSetDataService.setRobotReady(false);
} else if (data === "True") {
this.mqttSetDataService.setRobotReady(true);
}
}, 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);
});
//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()));
}
publishToGETBATTERY() {
this.mqttService.publishToTopic('BACKEND/ACTION/GETBATTERY');
}
publishToROBOTREADY() {
this.mqttService.publishToTopic('BACKEND/ACTION/ROBOTREADY');
}
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 wird die Spracheingabe übermittelt, die dann mit
* mehreren if else anweisungen einen Request an das Backend sendet und
* die Sprachausgabe mit next(..) triggert
* @method
*/
publishToINPUT(message: string) {
message = message.replace(/\s/g, ""); //entfernt alle leerzeichen
console.log(":" + message + ":"); //Nachricht die übermittel wurde, wird ausgegeben auf der Konsole
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 {
// schickt den Roboter zur eingegeben Pflanze..
this.storeService.getAllPlantNames().pipe(take(1)).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);
}
})
)
}
// Wenn kein case eintrifft, wird "Please try again" ausgegeben
if (!msgFound) {
msgFound = false;
this.sourceMessage.next("Please try again");
}
}
onDestroy() {
this.sourceMessage.next('');
}
}
@@ -0,0 +1,16 @@
// import { TestBed } from '@angular/core/testing';
// import { MqttSetDataService } from './mqtt-set-data.service';
// describe('MqttSetDataService', () => {
// let service: MqttSetDataService;
// beforeEach(() => {
// TestBed.configureTestingModule({});
// service = TestBed.inject(MqttSetDataService);
// });
// it('should be created', () => {
// expect(service).toBeTruthy();
// });
// });
@@ -0,0 +1,35 @@
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);
}
}
@@ -0,0 +1,16 @@
// import { TestBed } from '@angular/core/testing';
// import { MqttService } from './mqtt.service';
// describe('MqttService', () => {
// let service: MqttService;
// beforeEach(() => {
// TestBed.configureTestingModule({});
// service = TestBed.inject(MqttService);
// });
// it('should be created', () => {
// expect(service).toBeTruthy();
// });
// });
@@ -0,0 +1,37 @@
import { Injectable } from '@angular/core';
import { connect, MqttClient } from 'mqtt/dist/mqtt';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class MqttService {
private client: MqttClient;
// Connection zu dem MQTT Broker
constructor() {
this.client = connect('wss://mqtt.eclipseprojects.io:443/mqtt'); //Online Broker
// this.client = connect('mqtt://192.168.137.197:1883', { clientId: 'kemal' }); //lokaler Broker
}
public subscribeToTopic(topic: string): Observable<any> {
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);
}
}
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { StoreService } from './store.service';
describe('StoreService', () => {
let service: StoreService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(StoreService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
@@ -0,0 +1,98 @@
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;
}
}
@@ -0,0 +1,10 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
@@ -0,0 +1,44 @@
.container {
margin: 50px;
}
.icon-menu {
transform: scale(2);
}
span {
font-size: 1.5rem;
font-family: Cormorant;
}
header {
border: 0.125rem;
border-style: solid;
border-radius: 1rem;
border-color: #89bf54;
box-shadow: 0.2rem 0.2rem 0.2rem silver;
padding: 4px;
display: grid;
grid-template-columns: 20% 60% 20%;
font-family: Cormorant;
.menu-item {
grid-column: 1/2;
justify-self: start;
align-self: center;
}
.header-item {
grid-column: 2/3;
justify-self: center;
align-self: end;
font-size: 3rem;
}
.img-item {
grid-column: 3/4;
justify-self: end;
width: 15rem;
height: 3rem;
}
}
@@ -0,0 +1,35 @@
<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>
@@ -0,0 +1,59 @@
import { Location } from '@angular/common';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MatMenuModule } from '@angular/material/menu';
import { MqttRequestService } from './Service/Mqtt/mqtt-request.service';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
let component: AppComponent;
let fixture: ComponentFixture<AppComponent>;
let mqttRequestServiceSpy: jasmine.SpyObj<MqttRequestService>;
let locationStub: Partial<Location>;
beforeEach(async () => {
const mqttRequestServiceMock = jasmine.createSpyObj('MqttRequestService', ['someMethod']);
locationStub = {
path: () => '/home'
};
await TestBed.configureTestingModule({
imports: [MatMenuModule],
declarations: [AppComponent],
providers: [
{ provide: MqttRequestService, useValue: mqttRequestServiceMock },
{ provide: Location, useValue: locationStub }
]
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
mqttRequestServiceSpy = TestBed.inject(MqttRequestService) as jasmine.SpyObj<MqttRequestService>;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should set header to "Home" if the route is empty', () => {
expect(component.header).toBe('Home');
});
it('should set header to the capitalized route value', () => {
locationStub.path = () => '/about';
const componentNew = new AppComponent(mqttRequestServiceSpy, locationStub as Location);
expect(componentNew.header).toBe('About');
});
it('should update header when onMenuSelect is called', () => {
component.onMenuSelect('Map');
expect(component.header).toBe('Map');
});
});
@@ -0,0 +1,28 @@
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) {
// Header in der Leiste zu bestimmen
const actualRoute = this.location.path().slice(1);
const lastParam = actualRoute.charAt(0).toUpperCase() + actualRoute.slice(1);
if (lastParam === "") {
this.onMenuSelect('Home');
} else {
this.onMenuSelect(lastParam);
}
}
onMenuSelect(header: string) {
this.header = header;
}
}
+87
View File
@@ -0,0 +1,87 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatBadgeModule } from '@angular/material/badge';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatMenuModule } from '@angular/material/menu';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './Home/home.component';
import { InputComponent } from './Home/input/input.component';
import { OutputComponent } from './Home/output/output.component';
import { MapComponent } from './Map/map.component';
import { PlantCardComponent } from './Plants/plant-card/plant-card.component';
import { PlantsComponent } from './Plants/plants.component';
import { RobotComponent } from './Robot/robot.component';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { AddPlantDialogComponent } from './dialog/add-plant-dialog/add-plant-dialog.component';
import { ConfigurePlantDialogComponent } from './dialog/configure-plant-dialog/configure-plant-dialog.component';
import { ErrorDialogComponent } from './dialog/error-dialog/error-dialog.component';
import { MapPlantDialogComponent } from './dialog/map-plant-dialog/map-plant-dialog.component';
import { DeleteConfirmDialogComponent } from './dialog/delete-confirm-dialog/delete-confirm-dialog.component';
const appRoutes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'home', component: HomeComponent },
{ path: 'map', component: MapComponent },
{ path: 'plants', component: PlantsComponent },
{ path: 'robot', component: RobotComponent }
];
@NgModule({
declarations: [
AppComponent,
HomeComponent,
MapComponent,
InputComponent,
OutputComponent,
PlantsComponent,
PlantCardComponent,
RobotComponent,
ConfigurePlantDialogComponent,
AddPlantDialogComponent,
MapPlantDialogComponent,
ErrorDialogComponent,
DeleteConfirmDialogComponent
],
imports: [
BrowserModule,
AppRoutingModule,
MatMenuModule,
MatIconModule,
MatButtonModule,
HttpClientModule,
BrowserAnimationsModule,
MatBadgeModule,
RouterModule.forRoot(appRoutes),
MatFormFieldModule,
FormsModule,
MatInputModule,
MatCardModule,
ScrollingModule,
MatProgressBarModule,
MatAutocompleteModule,
ReactiveFormsModule,
MatDialogModule,
MatSelectModule,
MatProgressSpinnerModule
],
providers: [],
bootstrap: [AppComponent],
exports: [ConfigurePlantDialogComponent, AddPlantDialogComponent, MapPlantDialogComponent, ErrorDialogComponent]
})
export class AppModule { }
@@ -0,0 +1,78 @@
.item-header-content {
display: grid;
grid-template-columns: 11.25rem 6.25rem;
grid-template-rows: 20% 80%;
justify-content: start;
align-content: start;
}
.item-header {
grid-column: 1/-1;
grid-row: 1/2;
justify-self: start;
align-self: center;
font-size: 1.5625rem;
margin-bottom: 1rem;
color: blue;
display: grid;
grid-template-columns: 11.25rem 6.25rem;
}
.item-header-text {
grid-column: 1/2;
justify-self: center;
align-self: center;
}
.item-header-button {
grid-column: 2/3;
justify-self: end;
background-color: white;
transform: scale(0.8);
}
.icon-language {
color: black;
transform: scale(1.6);
}
.item-header-button:hover {
background-color: silver;
}
.item-Measurements-IconsAndNames {
grid-column: 1/2;
grid-row: 2/3;
justify-self: start;
align-self: center;
font-size: 1.125rem;
}
.item-Measurements-Values {
grid-column: 2/3;
justify-self: start;
align-self: center;
}
.item-IMG {
justify-self: center;
align-self: center;
}
p {
margin: 0;
}
.IMG-Plants {
border-radius: 0.5rem;
width: 11.25rem;
height: 9.375rem;
margin: 0rem 0.3125rem;
}
.icons-measurements {
width: 1rem;
height: 0.75rem;
margin: 0rem 0.3125rem;
margin-top: 0.4rem;
}
@@ -0,0 +1,41 @@
<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>
</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()">
<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>
@@ -0,0 +1,172 @@
// import { ComponentFixture, TestBed } from '@angular/core/testing';
// import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog';
// import { of } from 'rxjs';
// import { MqttRequestService } from 'src/app/Service/Mqtt/mqtt-request.service';
// import { StoreService } from 'src/app/Service/store.service';
// import { Plant } from 'src/app/models/plant.model';
// import { AddPlantDialogComponent } from './add-plant-dialog.component';
// const plantMock1: Plant = {
// AirTemperature: 25,
// AirHumidity: 60,
// SoilMoisture: 50,
// Brightness: 80,
// PlantID: 1,
// Timestamp: "2023-06-05T12:00:00",
// MeasurementID: "abcd1234",
// PlantName: "Plant1",
// };
// const plantMock2: Plant = {
// AirTemperature: 22,
// AirHumidity: 60,
// SoilMoisture: 50,
// Brightness: 80,
// PlantID: 1,
// Timestamp: "2023-06-05T12:00:00",
// MeasurementID: "abcd1234",
// PlantName: "Plant2",
// };
// const plantMock3: Plant = {
// AirTemperature: 21,
// AirHumidity: 60,
// SoilMoisture: 50,
// Brightness: 80,
// PlantID: 1,
// Timestamp: "2023-06-05T12:00:00",
// MeasurementID: "abcd1234",
// PlantName: "Plant3",
// };
// describe('AddPlantDialogComponent', () => {
// let component: AddPlantDialogComponent;
// let fixture: ComponentFixture<AddPlantDialogComponent>;
// let dialogRefSpyObj: jasmine.SpyObj<MatDialogRef<AddPlantDialogComponent>>;
// let dialogSpy: jasmine.SpyObj<MatDialog>;
// let mqttRequestServiceSpy: jasmine.SpyObj<MqttRequestService>;
// let storeServiceSpy: jasmine.SpyObj<StoreService>;
// beforeEach(async () => {
// const dialogRefSpy = jasmine.createSpyObj('MatDialogRef', ['close']);
// const dialogSpyObj = jasmine.createSpyObj('MatDialog', ['open']);
// const mqttRequestServiceSpyObj = jasmine.createSpyObj('MqttRequestService', [
// 'publishToPLANTCOUNT',
// 'publishToNEWPLANT',
// 'publishToGETALLDATA',
// ]);
// const storeServiceSpyObj = jasmine.createSpyObj('StoreService', [
// 'currentPlants',
// 'getIDsWithoutPlant',
// 'getAllPlantNames',
// ]);
// await TestBed.configureTestingModule({
// declarations: [AddPlantDialogComponent],
// providers: [
// { provide: MatDialogRef, useValue: dialogRefSpy },
// { provide: MAT_DIALOG_DATA, useValue: {} },
// { provide: MatDialog, useValue: dialogSpyObj },
// { provide: MqttRequestService, useValue: mqttRequestServiceSpyObj },
// { provide: StoreService, useValue: storeServiceSpyObj },
// ],
// }).compileComponents();
// });
// beforeEach(() => {
// fixture = TestBed.createComponent(AddPlantDialogComponent);
// component = fixture.componentInstance;
// dialogRefSpyObj = TestBed.inject(MatDialogRef) as jasmine.SpyObj<MatDialogRef<AddPlantDialogComponent>>;
// dialogSpy = TestBed.inject(MatDialog) as jasmine.SpyObj<MatDialog>;
// mqttRequestServiceSpy = TestBed.inject(MqttRequestService) as jasmine.SpyObj<MqttRequestService>;
// storeServiceSpy = TestBed.inject(StoreService) as jasmine.SpyObj<StoreService>;
// });
// it('should initialize with buttonSavedisabled set to true', () => {
// // Assert
// expect(component.buttonSavedisabled).toBe(true);
// });
// it('should call publishToPLANTCOUNT method and retrieve options', () => {
// // Arrange
// const currentPlantsMock = of([plantMock1, plantMock2, plantMock3]);
// const getIDsWithoutPlantMock = jasmine.createSpy().and.returnValue(of([4, 5, 6]));
// const getAllPlantNamesMock = jasmine.createSpy().and.returnValue(of(['Plant1', 'Plant2', 'Plant3']));
// // Act
// storeServiceSpy.currentPlants = currentPlantsMock;
// storeServiceSpy.getIDsWithoutPlant = getIDsWithoutPlantMock;
// storeServiceSpy.getAllPlantNames = getAllPlantNamesMock;
// fixture.detectChanges();
// // Assert
// expect(mqttRequestServiceSpy.publishToPLANTCOUNT).toHaveBeenCalled();
// expect(storeServiceSpy.getIDsWithoutPlant).toHaveBeenCalledWith([plantMock1, plantMock2, plantMock3]);
// expect(storeServiceSpy.getAllPlantNames).toHaveBeenCalled();
// expect(component.options$).toEqual(of([4, 5, 6]));
// expect(component.plantNames).toEqual(['Plant1', 'Plant2', 'Plant3']);
// });
// // it('should open the error dialog if plant input fails', () => {
// // // Arrange
// // const plantInputString = 'Plant Name is empty';
// // // Act
// // component.plantInputTestFails = jasmine.createSpy().and.returnValue(plantInputString);
// // component.onSaveDialog();
// // // Assert
// // expect(dialogSpy.open).toHaveBeenCalledWith(ErrorDialogComponent, {
// // data: 'Error: ' + plantInputString,
// // width: '400px',
// // });
// // expect(mqttRequestServiceSpy.publishToNEWPLANT).not.toHaveBeenCalled();
// // expect(mqttRequestServiceSpy.publishToGETALLDATA).not.toHaveBeenCalled();
// // expect(window.location.reload).not.toHaveBeenCalled();
// // });
// // it('should call the MQTT request service and reload the page if plant input is valid', () => {
// // // Arrange
// // const plantInputString = '';
// // // Act
// // component.plantInputTestFails = jasmine.createSpy().and.returnValue(plantInputString);
// // component.onSaveDialog();
// // // Assert
// // expect(dialogSpy.open).not.toHaveBeenCalled();
// // expect(mqttRequestServiceSpy.publishToNEWPLANT).toHaveBeenCalledWith(component.plant);
// // expect(mqttRequestServiceSpy.publishToGETALLDATA).toHaveBeenCalled();
// // expect(window.location.reload).toHaveBeenCalled();
// // });
// // it('should open the map plant dialog', () => {
// // // Act
// // component.onOpenMap();
// // // Assert
// // expect(dialogSpy.open).toHaveBeenCalledWith(MapPlantDialogComponent, {
// // width: '400px',
// // height: '400px',
// // });
// // });
// // it('should set buttonSavedisabled to false when option is selected within the range', () => {
// // // Arrange
// // component.buttonSavedisabled = true;
// // // Act
// // component.onOptionSelected(5);
// // // Assert
// // expect(component.buttonSavedisabled).toBeFalse();
// // });
// // it('should keep buttonSavedisabled as true when option is selected outside the range', () => {
// // // Arrange
// // component.buttonSavedisabled = true;
// // // Act
// // component.onOptionSelected(10);
// // // Assert
// // expect(component.buttonSavedisabled).toBeTrue();
// // });
// });
@@ -0,0 +1,124 @@
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']
})
/**
* @description
* Mit diesem Dialog "AddPlantDialogComponent" wird eine neue Pflanze erstellt und in das Backend geschickt.
*/
export class AddPlantDialogComponent implements OnInit {
selected: number;
options$: Observable<number[]>;
plantNames: string[];
plant = {} as Plant;
buttonSavedisabled: boolean;
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; //Button "Save" wird disabled
this.mqttRequestService.publishToPLANTCOUNT(); // Pflanzen anzahl wird angefordet#
// ID's bei denen noch keine Pflanze sind, werden in "option$" rein geschrieben
this.storeService.currentPlants.subscribe(response => {
this.options$ = this.storeService.getIDsWithoutPlant(response);
})
// Alle aktuellen Pflanzen Namen werden in "plantNames" geschrieben
this.storeService.getAllPlantNames().subscribe(result => {
this.plantNames = result
});
}
/**
* Nach dem der Button "Save" geklickt wurde, werden die Daten in die Pflanze geschrieben
* die anschließend geprüft wird auf zulässige Werte, falls unzulässige werte vorhanden sind,
* wird eine Fehlermeldung angezeigt
* @method onSaveDialog
*/
onSaveDialog() {
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();
// bei unzulässigen Werten --> Dialog, bei zulässigen Wertden --> Pflanze wird an das Backend geschickt
if (plantInputString) {
this.dialog.open(ErrorDialogComponent, {
data: "Error: " + plantInputString,
width: '400px',
});
} else {
this.mqttRequestService.publishToNEWPLANT(this.plant);
this.mqttRequestService.publishToGETALLDATA(); // Daten werden aus dem Backend angefragt
window.location.reload(); // Seite neu laden
}
}
/**
* Methode prüft ob die Werte die eingegeben wurden, korrekt sind.
* @method
* @return Bei einem Fehler --> Fehlermeldung; wenn es passt --> string oder leeren string
*/
plantInputTestFails(): string {
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 > 40000) {
return "Brightness is to high";
}
return ""; //Empty string is a Boolean false!
}
/**
* beim klick auf den Button, wird das Dialog "MapPlantDialogComponent" geöffnet
* @method
*/
onOpenMap() {
this.dialog.open(MapPlantDialogComponent, {
width: '400px',
height: '400px'
});
}
/**
* nach der Auswahl der ID, wird der Button "Save" enabled
* @method
*/
onOptionSelected() {
this.buttonSavedisabled = false;
}
}
@@ -0,0 +1,62 @@
.item-header-content {
display: grid;
grid-template-columns: 11.25rem 6rem 3rem;
grid-template-rows: 20% 80%;
justify-content: start;
align-content: start;
}
.item-header {
grid-column: 1/-1;
grid-row: 1/2;
justify-self: start;
align-self: center;
font-size: 1.6rem;
margin-bottom: 1rem;
color: blue;
}
.item-Measurements-IconsAndNames {
grid-column: 1/2;
grid-row: 2/3;
justify-self: start;
align-self: center;
}
.item-Measurements-Values {
grid-column: 2/3;
justify-self: start;
align-self: center;
}
.item-Measurements-Units {
grid-column: 3/4;
justify-self: start;
align-self: center;
}
.item-IMG {
justify-self: center;
align-self: center;
}
p {
margin: 0;
}
.IMG-Plants {
border-radius: 0.5rem;
width: 11.25rem;
height: 9.375rem;
margin: 0rem 0.3125rem;
}
.icons-measurements {
width: 1rem;
height: 0.75rem;
margin: 0rem 0.3125rem;
}
input {
width: 5rem;
}
@@ -0,0 +1,34 @@
<div class="item-header-content">
<div class="item-header">
<p>Configure Plant: {{plant.PlantName}}</p>
</div>
<div class="item-Measurements-IconsAndNames">
<img class="icons-measurements" src="assets/images/Icons-Card/air_humidity_icon.png" alt="not available">
Air Humidity: <br>
<img class="icons-measurements" src="assets/images/Icons-Card/air_temperature_icon.png" alt="not available">
Air Temperature: <br>
<img class="icons-measurements" src="assets/images/Icons-Card/soil_moisture_icon.png" alt="not available">
Soil Moisture: <br>
<img class="icons-measurements" src="assets/images/Icons-Card/brightness_icon.png" alt="not available">
Brightness:<br>
<!-- <img class="icons-measurements" src="assets/images/Icons-Card/Timestamp_icon.png" alt="not available">
Timestamp: -->
</div>
<div class="item-Measurements-Values">
<input type="number" [value]=plant.AirTemperature id="inputAirTemperature">
<input type="number" [value]=plant.AirHumidity id="inputAirHumidity">
<input type="number" [value]=plant.SoilMoisture id="inputSoilMoisture">
<input type="number" [value]=plant.Brightness id="inputBrightness">
<!-- <input type="datetime" [value]=plant.Timestamp id="inputTimestamp"> -->
</div>
<div class="item-Measurements-Units">
<p>%</p>
<p>°C</p>
<p>%</p>
<p>Lux</p>
</div>
</div>
<div mat-dialog-actions>
<button mat-button mat-dialog-close>No</button>
<button mat-button mat-dialog-close cdkFocusInitial (click)="onSaveDialog()">Save</button>
</div>
@@ -0,0 +1,23 @@
// import { ComponentFixture, TestBed } from '@angular/core/testing';
// import { ConfigurePlantDialogComponent } from './configure-plant-dialog.component';
// describe('ConfigurePlantDialogComponent', () => {
// let component: ConfigurePlantDialogComponent;
// let fixture: ComponentFixture<ConfigurePlantDialogComponent>;
// beforeEach(async () => {
// await TestBed.configureTestingModule({
// declarations: [ ConfigurePlantDialogComponent ]
// })
// .compileComponents();
// fixture = TestBed.createComponent(ConfigurePlantDialogComponent);
// component = fixture.componentInstance;
// fixture.detectChanges();
// });
// it('should create', () => {
// expect(component).toBeTruthy();
// });
// });
@@ -0,0 +1,69 @@
import { Component, Inject } 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 {
plant: Plant;
constructor(public dialogRef: MatDialogRef<ConfigurePlantDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
private mqttRequestService: MqttRequestService,
public dialog: MatDialog) {
this.plant = data.plant;
}
/**
* Nach dem der Button "Save" geklickt wurde, werden die Daten in die Pflanze geschrieben
* die anschließend geprüft wird auf zulässige Werte, falls unzulässige werte vorhanden sind,
* wird eine Fehlermeldung angezeigt
* @method onSaveDialog
*/
onSaveDialog() {
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();
// bei unzulässigen Werten --> Dialog, bei zulässigen Wertden --> Pflanze wird an das Backend geschickt
if (plantInputString) {
this.dialog.open(ErrorDialogComponent, {
data: "Error: " + plantInputString,
width: '400px',
});
} else {
this.mqttRequestService.publishToCONFIGUREPLANT(this.plant); //Request to the Backend, to change the data of the plant
this.mqttRequestService.publishToGETALLDATA(); // Daten werden aus dem Backend angefragt
window.location.reload(); // Seite neu laden
}
}
/**
* Methode prüft ob die Werte die eingegeben wurden, korrekt sind.
* @method
* @return Bei einem Fehler --> Fehlermeldung; wenn es passt --> string oder leeren string
*/
plantInputTestFails(): string {
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 > 40000) {
return "Brightness is to high";
}
return ""; //Empty string is a Boolean false!
}
}
@@ -0,0 +1,10 @@
:host ::ng-deep {
.mat-dialog-title {
color: red;
}
.mat-button {
background-color: ghostwhite;
}
}
@@ -0,0 +1,8 @@
<div class="container">
<p mat-dialog-title>Delete Plant: {{plant.PlantName}}</p>
<p mat-dialog-content>Soll die Pflanze "{{plant.PlantName}}" tatsächlich gelöscht werden?</p>
<div mat-dialog-actions>
<button mat-button mat-dialog-close>No</button>
<button mat-button mat-dialog-close cdkFocusInitial (click)="onDelete()" color="warn">Delete</button>
</div>
</div>

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