Compare commits
99
Commits
bb09d15a56
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
556736ccf5 | ||
|
|
410e27adc3 | ||
|
|
f6f6421620 | ||
|
|
5e14633f42 | ||
|
|
1778a211bb | ||
|
|
22bcf7310f | ||
|
|
90235a73f1 | ||
|
|
2c065573a5 | ||
|
|
2a3136503a | ||
|
|
d9ad3bce0c | ||
|
|
e975585f57 | ||
|
|
9b1bcbebde | ||
|
|
a857f870a5 | ||
|
|
847a424049 | ||
|
|
d06f0a46e7 | ||
|
|
9166d4040e | ||
|
|
a13b150dfe | ||
|
|
445373a668 | ||
|
|
bd1daa0489 | ||
|
|
5e21e4b2c5 | ||
|
|
a368637346 | ||
|
|
99b1bbb065 | ||
|
|
a8f365cd4f | ||
|
|
052ace21bf | ||
|
|
612e3b1b93 | ||
|
|
8fa5cae836 | ||
|
|
4cadbf9c29 | ||
|
|
409c38a96d | ||
|
|
6964f9855e | ||
|
|
a0c7bc9077 | ||
|
|
b70d445480 | ||
|
|
93b4075c35 | ||
|
|
4e60cd9ad0 | ||
|
|
062d08fe1a | ||
|
|
7452d6aae8 | ||
|
|
43fbbfcbbb | ||
|
|
0385aa7792 | ||
|
|
c35028f354 | ||
|
|
a16c313a6c | ||
|
|
35118e8c20 | ||
|
|
b8cb4f6217 | ||
|
|
48a4bbe710 | ||
|
|
0dc80b8981 | ||
|
|
1502da2b11 | ||
|
|
9f12f21ac4 | ||
|
|
56f9065fc7 | ||
|
|
a6259e5392 | ||
|
|
20bad535eb | ||
|
|
830d662e08 | ||
|
|
0a53fe171a | ||
|
|
9832c4e986 | ||
|
|
d3ee161444 | ||
|
|
1e4fb4c4b4 | ||
|
|
ae1e67f204 | ||
|
|
58bdd595d5 | ||
|
|
24268d3039 | ||
|
|
5338490503 | ||
|
|
b798e138fa | ||
|
|
0c08ab1d8b | ||
|
|
33e2a058d3 | ||
|
|
cd07f728ee | ||
|
|
c4ef41de90 | ||
|
|
8efbe4fc71 | ||
|
|
f3fab66cb5 | ||
|
|
d54bfdd101 | ||
|
|
307e9fcb7c | ||
|
|
6adc5a3426 | ||
|
|
57cadbbbf0 | ||
|
|
5c02b0a2bc | ||
|
|
14fea73572 | ||
|
|
9396c59e86 | ||
|
|
f29e567f07 | ||
|
|
bbc895c7dd | ||
|
|
6e66611e06 | ||
|
|
9b32acdc68 | ||
|
|
3d0bc3c4d9 | ||
|
|
60be6862b0 | ||
|
|
9388f765f4 | ||
|
|
a3e3da1297 | ||
|
|
d27076ba54 | ||
|
|
7f1af76a55 | ||
|
|
3bb6f8c2c7 | ||
|
|
3f1176917a | ||
|
|
c93d1c4794 | ||
|
|
48f3dc9499 | ||
|
|
70559870a5 | ||
|
|
7b9f560da5 | ||
|
|
e39b2a2753 | ||
|
|
fc545d59a1 | ||
|
|
3a4a8e11a9 | ||
|
|
a941297323 | ||
|
|
f93b7d4f55 | ||
|
|
4cfc6442a7 | ||
|
|
969524966c | ||
|
|
f5af78ffe4 | ||
|
|
8058012260 | ||
|
|
b155fcb6a4 | ||
|
|
3878020331 | ||
|
|
057dd56e2c |
@@ -114,3 +114,8 @@ dmypy.json
|
|||||||
# Pyre type checker
|
# Pyre type checker
|
||||||
.pyre/
|
.pyre/
|
||||||
|
|
||||||
|
# VSCode
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
#
|
||||||
|
~*
|
||||||
Generated
+36
@@ -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>
|
||||||
Generated
+15
@@ -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
@@ -4,7 +4,7 @@
|
|||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="jdk" jdkName="Python 3.10 (projektarbeit_duelger_waldhauser_caliskan)" jdkType="Python SDK" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
Generated
+7
@@ -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>
|
||||||
Generated
+8
@@ -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>
|
||||||
Generated
+14
@@ -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>
|
||||||
Vendored
-3
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"github-enterprise.uri": "https://git.efi.th-nuernberg.de"
|
|
||||||
}
|
|
||||||
Binary file not shown.
@@ -0,0 +1,3 @@
|
|||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.venv/
|
||||||
+13
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
+15
@@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+7
@@ -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/
|
||||||
+13
@@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+7
@@ -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))
|
||||||
@@ -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
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
print("Hello World")
|
||||||
Binary file not shown.
Binary file not shown.
@@ -3504,7 +3504,8 @@
|
|||||||
"_parent": {
|
"_parent": {
|
||||||
"$ref": "AAAAAAGHMigXUpG7rpw="
|
"$ref": "AAAAAAGHMigXUpG7rpw="
|
||||||
},
|
},
|
||||||
"name": "Role1"
|
"name": "Role1",
|
||||||
|
"type": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"_type": "UMLAttribute",
|
"_type": "UMLAttribute",
|
||||||
@@ -3512,7 +3513,8 @@
|
|||||||
"_parent": {
|
"_parent": {
|
||||||
"$ref": "AAAAAAGHMigXUpG7rpw="
|
"$ref": "AAAAAAGHMigXUpG7rpw="
|
||||||
},
|
},
|
||||||
"name": "Role2"
|
"name": "Role2",
|
||||||
|
"type": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"_type": "UMLAttribute",
|
"_type": "UMLAttribute",
|
||||||
@@ -3520,7 +3522,242 @@
|
|||||||
"_parent": {
|
"_parent": {
|
||||||
"$ref": "AAAAAAGHMigXUpG7rpw="
|
"$ref": "AAAAAAGHMigXUpG7rpw="
|
||||||
},
|
},
|
||||||
"name": "Role3"
|
"name": "Role3",
|
||||||
|
"type": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_type": "UMLModel",
|
||||||
|
"_id": "AAAAAAGHSvOnT6lLzLI=",
|
||||||
|
"_parent": {
|
||||||
|
"$ref": "AAAAAAFF+h6SjaM2Hec="
|
||||||
|
},
|
||||||
|
"name": "Model1",
|
||||||
|
"ownedElements": [
|
||||||
|
{
|
||||||
|
"_type": "UMLClassDiagram",
|
||||||
|
"_id": "AAAAAAGHSvOnUKlMd1M=",
|
||||||
|
"_parent": {
|
||||||
|
"$ref": "AAAAAAGHSvOnT6lLzLI="
|
||||||
|
},
|
||||||
|
"name": "ClassDiagram1",
|
||||||
|
"ownedViews": [
|
||||||
|
{
|
||||||
|
"_type": "UMLClassView",
|
||||||
|
"_id": "AAAAAAGHSvO0JalSVe0=",
|
||||||
|
"_parent": {
|
||||||
|
"$ref": "AAAAAAGHSvOnUKlMd1M="
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"$ref": "AAAAAAGHSvO0JalQDB0="
|
||||||
|
},
|
||||||
|
"subViews": [
|
||||||
|
{
|
||||||
|
"_type": "UMLNameCompartmentView",
|
||||||
|
"_id": "AAAAAAGHSvO0JalTHAU=",
|
||||||
|
"_parent": {
|
||||||
|
"$ref": "AAAAAAGHSvO0JalSVe0="
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"$ref": "AAAAAAGHSvO0JalQDB0="
|
||||||
|
},
|
||||||
|
"subViews": [
|
||||||
|
{
|
||||||
|
"_type": "LabelView",
|
||||||
|
"_id": "AAAAAAGHSvO0JalU/Lk=",
|
||||||
|
"_parent": {
|
||||||
|
"$ref": "AAAAAAGHSvO0JalTHAU="
|
||||||
|
},
|
||||||
|
"visible": false,
|
||||||
|
"font": "Arial;13;0",
|
||||||
|
"height": 13
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_type": "LabelView",
|
||||||
|
"_id": "AAAAAAGHSvO0JalVXXc=",
|
||||||
|
"_parent": {
|
||||||
|
"$ref": "AAAAAAGHSvO0JalTHAU="
|
||||||
|
},
|
||||||
|
"font": "Arial;13;1",
|
||||||
|
"left": 637,
|
||||||
|
"top": 119,
|
||||||
|
"width": 190,
|
||||||
|
"height": 13,
|
||||||
|
"text": "RaspySensors"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_type": "LabelView",
|
||||||
|
"_id": "AAAAAAGHSvO0JalW0mo=",
|
||||||
|
"_parent": {
|
||||||
|
"$ref": "AAAAAAGHSvO0JalTHAU="
|
||||||
|
},
|
||||||
|
"visible": false,
|
||||||
|
"font": "Arial;13;0",
|
||||||
|
"width": 80.9072265625,
|
||||||
|
"height": 13,
|
||||||
|
"text": "(from Model1)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_type": "LabelView",
|
||||||
|
"_id": "AAAAAAGHSvO0JalXjv8=",
|
||||||
|
"_parent": {
|
||||||
|
"$ref": "AAAAAAGHSvO0JalTHAU="
|
||||||
|
},
|
||||||
|
"visible": false,
|
||||||
|
"font": "Arial;13;0",
|
||||||
|
"height": 13,
|
||||||
|
"horizontalAlignment": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"font": "Arial;13;0",
|
||||||
|
"left": 632,
|
||||||
|
"top": 112,
|
||||||
|
"width": 200,
|
||||||
|
"height": 25,
|
||||||
|
"stereotypeLabel": {
|
||||||
|
"$ref": "AAAAAAGHSvO0JalU/Lk="
|
||||||
|
},
|
||||||
|
"nameLabel": {
|
||||||
|
"$ref": "AAAAAAGHSvO0JalVXXc="
|
||||||
|
},
|
||||||
|
"namespaceLabel": {
|
||||||
|
"$ref": "AAAAAAGHSvO0JalW0mo="
|
||||||
|
},
|
||||||
|
"propertyLabel": {
|
||||||
|
"$ref": "AAAAAAGHSvO0JalXjv8="
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_type": "UMLAttributeCompartmentView",
|
||||||
|
"_id": "AAAAAAGHSvO0JqlYsbg=",
|
||||||
|
"_parent": {
|
||||||
|
"$ref": "AAAAAAGHSvO0JalSVe0="
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"$ref": "AAAAAAGHSvO0JalQDB0="
|
||||||
|
},
|
||||||
|
"font": "Arial;13;0",
|
||||||
|
"left": 632,
|
||||||
|
"top": 137,
|
||||||
|
"width": 200,
|
||||||
|
"height": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_type": "UMLOperationCompartmentView",
|
||||||
|
"_id": "AAAAAAGHSvO0JqlZXW4=",
|
||||||
|
"_parent": {
|
||||||
|
"$ref": "AAAAAAGHSvO0JalSVe0="
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"$ref": "AAAAAAGHSvO0JalQDB0="
|
||||||
|
},
|
||||||
|
"subViews": [
|
||||||
|
{
|
||||||
|
"_type": "UMLOperationView",
|
||||||
|
"_id": "AAAAAAGHSvPxiKl9u9A=",
|
||||||
|
"_parent": {
|
||||||
|
"$ref": "AAAAAAGHSvO0JqlZXW4="
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"$ref": "AAAAAAGHSvPxg6l6DOE="
|
||||||
|
},
|
||||||
|
"font": "Arial;13;0",
|
||||||
|
"left": 637,
|
||||||
|
"top": 152,
|
||||||
|
"width": 190,
|
||||||
|
"height": 13,
|
||||||
|
"text": "+measureAll(): Dictionary",
|
||||||
|
"horizontalAlignment": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"font": "Arial;13;0",
|
||||||
|
"left": 632,
|
||||||
|
"top": 147,
|
||||||
|
"width": 200,
|
||||||
|
"height": 23
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_type": "UMLReceptionCompartmentView",
|
||||||
|
"_id": "AAAAAAGHSvO0JqlauSU=",
|
||||||
|
"_parent": {
|
||||||
|
"$ref": "AAAAAAGHSvO0JalSVe0="
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"$ref": "AAAAAAGHSvO0JalQDB0="
|
||||||
|
},
|
||||||
|
"visible": false,
|
||||||
|
"font": "Arial;13;0",
|
||||||
|
"width": 10,
|
||||||
|
"height": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_type": "UMLTemplateParameterCompartmentView",
|
||||||
|
"_id": "AAAAAAGHSvO0Jqlbl7Q=",
|
||||||
|
"_parent": {
|
||||||
|
"$ref": "AAAAAAGHSvO0JalSVe0="
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"$ref": "AAAAAAGHSvO0JalQDB0="
|
||||||
|
},
|
||||||
|
"visible": false,
|
||||||
|
"font": "Arial;13;0",
|
||||||
|
"width": 10,
|
||||||
|
"height": 10
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"font": "Arial;13;0",
|
||||||
|
"containerChangeable": true,
|
||||||
|
"left": 632,
|
||||||
|
"top": 112,
|
||||||
|
"width": 200,
|
||||||
|
"height": 280,
|
||||||
|
"nameCompartment": {
|
||||||
|
"$ref": "AAAAAAGHSvO0JalTHAU="
|
||||||
|
},
|
||||||
|
"attributeCompartment": {
|
||||||
|
"$ref": "AAAAAAGHSvO0JqlYsbg="
|
||||||
|
},
|
||||||
|
"operationCompartment": {
|
||||||
|
"$ref": "AAAAAAGHSvO0JqlZXW4="
|
||||||
|
},
|
||||||
|
"receptionCompartment": {
|
||||||
|
"$ref": "AAAAAAGHSvO0JqlauSU="
|
||||||
|
},
|
||||||
|
"templateParameterCompartment": {
|
||||||
|
"$ref": "AAAAAAGHSvO0Jqlbl7Q="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_type": "UMLClass",
|
||||||
|
"_id": "AAAAAAGHSvO0JalQDB0=",
|
||||||
|
"_parent": {
|
||||||
|
"$ref": "AAAAAAGHSvOnT6lLzLI="
|
||||||
|
},
|
||||||
|
"name": "RaspySensors",
|
||||||
|
"operations": [
|
||||||
|
{
|
||||||
|
"_type": "UMLOperation",
|
||||||
|
"_id": "AAAAAAGHSvPxg6l6DOE=",
|
||||||
|
"_parent": {
|
||||||
|
"$ref": "AAAAAAGHSvO0JalQDB0="
|
||||||
|
},
|
||||||
|
"name": "measureAll",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"_type": "UMLParameter",
|
||||||
|
"_id": "AAAAAAGHSvSoBamCDuA=",
|
||||||
|
"_parent": {
|
||||||
|
"$ref": "AAAAAAGHSvPxg6l6DOE="
|
||||||
|
},
|
||||||
|
"type": "Dictionary",
|
||||||
|
"direction": "return"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+26
-1
@@ -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
|
certifi==2022.12.7
|
||||||
charset-normalizer==3.0.1
|
charset-normalizer==3.0.1
|
||||||
click==8.1.3
|
click==8.1.3
|
||||||
Flask==2.2.3
|
Flask==2.2.3
|
||||||
idna==3.4
|
idna==3.4
|
||||||
|
importlib-metadata==6.1.0
|
||||||
itsdangerous==2.1.2
|
itsdangerous==2.1.2
|
||||||
Jinja2==3.1.2
|
Jinja2==3.1.2
|
||||||
MarkupSafe==2.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
|
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
|
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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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.
|
||||||
@@ -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": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
});
|
||||||
|
};
|
||||||
Generated
+21168
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+62
@@ -0,0 +1,62 @@
|
|||||||
|
.item-header-content {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 11.25rem 6rem 3rem;
|
||||||
|
grid-template-rows: 20% 80%;
|
||||||
|
justify-content: start;
|
||||||
|
align-content: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-header {
|
||||||
|
grid-column: 1/-1;
|
||||||
|
grid-row: 1/2;
|
||||||
|
justify-self: start;
|
||||||
|
align-self: center;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-Measurements-IconsAndNames {
|
||||||
|
grid-column: 1/2;
|
||||||
|
grid-row: 2/3;
|
||||||
|
justify-self: start;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-Measurements-Values {
|
||||||
|
grid-column: 2/3;
|
||||||
|
justify-self: start;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-Measurements-Units {
|
||||||
|
grid-column: 3/4;
|
||||||
|
justify-self: start;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-IMG {
|
||||||
|
justify-self: center;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.IMG-Plants {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
width: 11.25rem;
|
||||||
|
height: 9.375rem;
|
||||||
|
margin: 0rem 0.3125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icons-measurements {
|
||||||
|
width: 1rem;
|
||||||
|
height: 0.75rem;
|
||||||
|
margin: 0rem 0.3125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 5rem;
|
||||||
|
}
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
<div class="item-header-content">
|
||||||
|
<div class="item-header">
|
||||||
|
<p>Configure Plant: {{plant.PlantName}}</p>
|
||||||
|
</div>
|
||||||
|
<div class="item-Measurements-IconsAndNames">
|
||||||
|
<img class="icons-measurements" src="assets/images/Icons-Card/air_humidity_icon.png" alt="not available">
|
||||||
|
Air Humidity: <br>
|
||||||
|
<img class="icons-measurements" src="assets/images/Icons-Card/air_temperature_icon.png" alt="not available">
|
||||||
|
Air Temperature: <br>
|
||||||
|
<img class="icons-measurements" src="assets/images/Icons-Card/soil_moisture_icon.png" alt="not available">
|
||||||
|
Soil Moisture: <br>
|
||||||
|
<img class="icons-measurements" src="assets/images/Icons-Card/brightness_icon.png" alt="not available">
|
||||||
|
Brightness:<br>
|
||||||
|
<!-- <img class="icons-measurements" src="assets/images/Icons-Card/Timestamp_icon.png" alt="not available">
|
||||||
|
Timestamp: -->
|
||||||
|
</div>
|
||||||
|
<div class="item-Measurements-Values">
|
||||||
|
<input type="number" [value]=plant.AirTemperature id="inputAirTemperature">
|
||||||
|
<input type="number" [value]=plant.AirHumidity id="inputAirHumidity">
|
||||||
|
<input type="number" [value]=plant.SoilMoisture id="inputSoilMoisture">
|
||||||
|
<input type="number" [value]=plant.Brightness id="inputBrightness">
|
||||||
|
<!-- <input type="datetime" [value]=plant.Timestamp id="inputTimestamp"> -->
|
||||||
|
</div>
|
||||||
|
<div class="item-Measurements-Units">
|
||||||
|
<p>%</p>
|
||||||
|
<p>°C</p>
|
||||||
|
<p>%</p>
|
||||||
|
<p>Lux</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div mat-dialog-actions>
|
||||||
|
<button mat-button mat-dialog-close>No</button>
|
||||||
|
<button mat-button mat-dialog-close cdkFocusInitial (click)="onSaveDialog()">Save</button>
|
||||||
|
</div>
|
||||||
+23
@@ -0,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();
|
||||||
|
// });
|
||||||
|
// });
|
||||||
+69
@@ -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!
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
:host ::ng-deep {
|
||||||
|
.mat-dialog-title {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-button {
|
||||||
|
background-color: ghostwhite;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user