Compare commits

...

36 commits

Author SHA1 Message Date
Matthias Guzmits
36d4379ddc Merge branch 'master' of https://github.com/F-WuTS/compLIB 2023-07-19 19:18:16 +02:00
Matthias Guzmits
c593059fb1 Add button functionality 2023-07-19 19:17:59 +02:00
Matthias Guzmits
ed04957d94 Add drive arc functionality 2023-07-19 18:36:07 +02:00
itssme
27962d16a7
(MAJOR) Bump to new major version after deleting invalid tag 2023-07-18 09:18:33 +02:00
itssme
9c40daae88
(MAJOR) Bump to new major version 2023-07-18 09:17:25 +02:00
Matthias Guzmits
de112d98e4 Added basic drive functions 2023-07-18 00:44:34 +02:00
Joel Klimont
fa4cc1a0ad fixed syntax error in postinstallscript 2023-02-10 14:29:13 +01:00
Joel Klimont
7e2977a653 fixed syntax error in postinstallscript 2023-02-10 14:02:07 +01:00
Joel Klimont
cf415d38e8 fixed documentation 2023-02-10 13:44:28 +01:00
Joel Klimont
f09d20ade2 removed .idea folder from repo 2023-01-14 15:11:57 +01:00
Joel Klimont
671292a6cf fixed moving built package to the correct output directory 2022-12-18 15:08:20 +01:00
itssme
b95b9bf518
(MINOR) updated readme 2022-12-18 14:00:11 +01:00
Konstantin Lampalzer
dcef53712f fix keyerror setup py 2022-12-18 00:03:26 +01:00
Konstantin Lampalzer
c02cfcd71c Move client foler 2022-12-17 23:59:06 +01:00
Konstantin Lampalzer
4c24717278 Skip posinstall on x86_64 2022-12-17 23:40:58 +01:00
Joel Klimont
19d98f1f04 nginx server is now stopped, as it is not needed anymore 2022-11-24 15:32:15 +01:00
Joel Klimont
13eefdceb1 fixed a bug where the postinstall script would fail, if "raspi-config" is not installed on the system 2022-11-19 01:42:11 +01:00
Joel Klimont
f03df1b3b3 updated opencv documentation (camera module)
added enabling of legacy support for the camera module to the postinstall script
2022-11-18 17:19:40 +01:00
Konstantin Lampalzer
ee4f6a516d Update faq 2022-11-18 15:47:47 +01:00
Konstantin Lampalzer
9e589fd681 Change motor speed to cm/s 2022-11-13 02:52:30 +01:00
Joel Klimont
be9a5c9f19 added env variable to overwrite seeding seed when in competition mode 2022-11-11 17:47:40 +01:00
Joel Klimont
765336231b added updating instructions to documentation 2022-11-11 17:22:01 +01:00
Joel Klimont
f2724e79f7 added example code from guide to test 2022-11-10 23:14:09 +01:00
Joel Klimont
a5274c2232 function to set random seed and to generate a random number are now private static functions in the Seeding gamestate 2022-11-09 17:07:03 +01:00
Joel Klimont
67947116ec added debug logger in seeding 2022-11-09 15:50:15 +01:00
Joel Klimont
369692b619 added german documentation to seeding and double elimination api/ gamestate 2022-11-09 03:25:27 +01:00
Joel Klimont
de9b671f29 added new Camera class for opencv video processing (currently does not work with Pi-Camera)
changed logging a bit
2022-11-05 21:18:24 +01:00
itssme
b87e830ac3
added missing dependencies 2022-11-05 01:57:58 +01:00
Konstantin Lampalzer
ff2c529d3a Update docs 2022-11-01 23:06:33 +01:00
itssme
e4592e7963
removed compsrv as dependency 2022-10-28 19:23:20 +02:00
Konstantin Lampalzer
285996f467 Update docu 2022-10-28 11:50:41 +02:00
Konstantin Lampalzer
bd0f14a83b Add qa program 2022-10-14 00:35:56 +02:00
Konstantin Lampalzer
1ee4a1e1ef Add linefollower 2022-10-14 00:10:07 +02:00
Konstantin Lampalzer
002db9d650 Add linefollower 2022-10-14 00:08:59 +02:00
Konstantin Lampalzer
f6e45ac25d update documentation 2022-10-13 00:55:54 +02:00
Konstantin Lampalzer
af3aaf7998 update documentation 2022-10-13 00:31:43 +02:00
92 changed files with 1515 additions and 527 deletions

View file

View file

@ -1,7 +1,11 @@
# compLIB
Rewrite for ROS is the live packaged version since 18.07.2023.
# Dependencies
TODO: document
## Building documentation
```
pip install sphinx-rtd-theme
@ -14,6 +18,13 @@ pip install sphinx-rtd-theme
[Inline documentation example](https://pythonhosted.org/an_example_pypi_project/sphinx.html#full-code-example)
[reStructured Text](https://pythonhosted.org/an_example_pypi_project/sphinx.html#restructured-text-rest-resources)
# ENV Variables
+ `DEBUG`, default="0", If set to != "0" (default), debug prints will be enabled
+ `API_URL`, default="http://localhost:5000/"
+ `API_FORCE`, default="", if set to !="" (default), it will replace the API_URL env variable
+ `FORCE_SEED`, default="-1", if set to !="-1" (default), the seeding seed supplied by the user will be ignored and this seed will be used instead
# Stream Video
```
@ -31,3 +42,9 @@ with ffmpeg, cpu friendly
```
ffmpeg -f v4l2 -framerate 30 -video_size 640x480 -i /dev/video0 -b:v 2M -f flv rtmp://10.20.86.88/live/stream
```
# Bullseye now only supports libcamera
https://www.raspberrypi.com/news/bullseye-camera-system/
(This can still be mitigated by enabling "old camera support" in the raspi-config settings. (This is done automatically in the postinstallscript)

View file

@ -4,7 +4,6 @@ mkdir output
DEB="empty"
cd client
source build_deb.sh
echo "Ran build deb, created: $DEB"
mv $DEB ../output
mv $DEB ./output

View file

@ -33,7 +33,9 @@ fpm -s python --python-bin python3 --python-package-name-prefix python3 \
-d "opencv-main" \
-d "opencv-python" \
-d "opencv-scripts" \
-d "compsrv" \
-d "libprotobuf23" \
-d "protobuf-compiler" \
-d "python3-protobuf" \
--python-install-lib "/usr/local/lib/python3.9/dist-packages" \
-v $VERSION -t deb setup.py
@ -56,4 +58,4 @@ echo "Created: $DEB"
# sudo apt purge python3-complib -y
# sudo apt install ./python3-*
# sudo apt search complib
# ar vx ./python3*
# ar vx ./python3*

3
client/.idea/.gitignore generated vendored
View file

@ -1,3 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View file

@ -1,12 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredIdentifiers">
<list>
<option value="list.__getitem__" />
</list>
</option>
</inspection_tool>
</profile>
</component>

View file

@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
client/.idea/misc.xml generated
View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9" project-jdk-type="Python SDK" />
<component name="PyCharmProfessionalAdvertiser">
<option name="shown" value="true" />
</component>
</project>

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/client_s2.iml" filepath="$PROJECT_DIR$/.idea/client_s2.iml" />
</modules>
</component>
</project>

View file

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SaveActionSettings">
<option name="actions">
<set>
<option value="activate" />
<option value="activateOnShortcut" />
<option value="activateOnBatch" />
<option value="organizeImports" />
<option value="reformat" />
<option value="rearrange" />
</set>
</option>
</component>
</project>

6
client/.idea/vcs.xml generated
View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

View file

@ -1,24 +0,0 @@
import time
from compLib.CompLibClient import CompLibClient
def main():
from compLib.Motor import Motor
# Motor.speed(0, -50)
# Motor.speed(3, 50)
Motor.power(0, 50)
Motor.power(3, -50)
time.sleep(2)
Motor.power(0, 0)
Motor.power(3, -0)
if __name__ == '__main__':
CompLibClient.use_tcp_socket("dev01.local")
# follow()
main()

View file

@ -1,34 +0,0 @@
import time
from compLib.CompLibClient import CompLibClient
from compLib.IRSensor import IRSensor
def main():
# Motor.speed(0, -50)
# Motor.speed(3, 50)
# Motor.power(0, 50)
# Motor.power(3, -50)
#
# time.sleep(2)
#
# Motor.power(0, 0)
# Motor.power(3, -0)
start_time = time.time()
for i in range(0, 1000):
IRSensor.read_all()
# Motor.multiple_power((0, 1), (3, 1))
# Motor.speed(0, 1)
# Motor.speed(3, 1)
print(1000.0 / (time.time() - start_time))
if __name__ == '__main__':
# CompLibClient.use_tcp_socket("dev03.local")
CompLibClient.use_unix_socket()
# follow()
# cProfile.run("main()")
main()

View file

@ -1,12 +0,0 @@
FAQ
###
Was ist das Passwort für die Entwicklungsumgebung?
--------------------------------------------------
``compair``
Wie verbinde ich mich zur Entwicklungsumgebung?
-----------------------------------------------
See :ref:`gettingstarted_codeserver`

View file

@ -1,8 +0,0 @@
compLib
#######
.. toctree::
:maxdepth: 5
:glob:
classes/*

View file

@ -1,32 +0,0 @@
.. _other_usage:
Beispiele
#########
Vorwärts und rückwärts fahren
*****************************
.. code-block:: python
import time
from compLib.Motor import *
def forward():
Motor.power(1, -30);
Motor.power(2, 30);
def backward():
Motor.power(1, 30);
Motor.power(2, -30);
def main():
print("hallo ich bin ein roboter beep buup")
forward()
time.sleep(1)
backward()
time.sleep(1)
if __name__ == '__main__':
main()

View file

@ -1,12 +0,0 @@
.. _software_installation:
Installationsanweisungen
########################
Diese Anleitung dient dazu die Software auf dem Roboter neu aufzusetzen. Im normalen Gebraucht sollte dies jedoch nicht notwendig sein.
Betriebssystem aufsetzen
========================
Als Basis wird für den Roboter Raspberry Pi OS (64-bit) verwendet. Das 32-Bit Betriebssystem wird nicht unterstützt, da die Software-Komponenten nur für aarch64 bzw. arm64/v8 kompiliert werden.
Genauere Informationen sind `hier <https://www.raspberrypi.com/software/operating-systems/>`_ zu finden.

View file

@ -1,96 +0,0 @@
from compLib.Motor import Motor
from compLib.IRSensor import IRSensor
from compLib.CompLibClient import CompLibClient
import time
import math
DRIVE_SPEED = 20.0
COLOR_BREAK = 850
KP = 7.5
KD = 0.0
SAMPLE_TIME_S = 0.001
CUTOFF_FREQ_HZ = 50.0
RC = 1.0 / (2.0 * math.pi * CUTOFF_FREQ_HZ)
FIRST_COEFF = SAMPLE_TIME_S / (SAMPLE_TIME_S + RC)
SECOND_COEFF = RC / (SAMPLE_TIME_S + RC)
out_old = 0.0
start_time = time.time()
def drive(leftSpeed, rightSpeed):
rightSpeed *= -0.906
Motor.multiple_power((0, min(max(-100, rightSpeed), 100)), (3, min(max(-100, leftSpeed), 100)))
def follow(sleepTime = 0.3):
global out_old
lastError = 0
sensorsBlack = 0
while sensorsBlack < 3:
sensor_data = IRSensor.read_all()
sensorsBlack = 0
for i in range(0, 5):
if sensor_data[i] > COLOR_BREAK:
sensorsBlack += 1
middle_sensor = sensor_data[2]
filtered_sensor = FIRST_COEFF * middle_sensor + SECOND_COEFF * out_old
out_old = filtered_sensor
sample_time = str(time.time() - start_time).replace(".", ",")
print(f"{sample_time} {middle_sensor} {int(filtered_sensor)}")
error = lastError
if sensor_data[2] > COLOR_BREAK:
error = 0
elif sensor_data[0] > COLOR_BREAK:
error = -1.5
elif sensor_data[4] > COLOR_BREAK:
error = 1.5
elif sensor_data[1] > COLOR_BREAK:
error = -1
elif sensor_data[3] > COLOR_BREAK:
error = 1
elif error == 1.5:
error = 3
elif error == -1.5:
error = -3
lastError = error
adjustment = KP * error + KD * (error - lastError)
leftSpeed = DRIVE_SPEED + adjustment
rightSpeed = DRIVE_SPEED - adjustment
# print(f"{leftSpeed} {rightSpeed} {adjustment} {error}")
drive(leftSpeed, rightSpeed)
time.sleep(SAMPLE_TIME_S)
drive(0, 0)
time.sleep(sleepTime)
def main():
CompLibClient.use_unix_socket()
IRSensor.enable()
time.sleep(0.5)
# while True:
# print(IRSensor.read_all())
# time.sleep(0.01)
follow()
follow()
follow()
follow()
follow(0.2)
main()

View file

@ -1,174 +0,0 @@
import time
from compLib.CompLibClient import CompLibClient
# def send(data, size):
# with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
# sock.connect(SOCKET_PATH)
# sock.sendall(size.to_bytes(1, byteorder='big'))
# sock.sendall(data)
#
# response_size_bytes = sock.recv(1)
# response_size = int.from_bytes(response_size_bytes, byteorder="big")
# # print(f"Response size: {response_size}")
#
# response_bytes = sock.recv(response_size)
# generic_response = CompLib_pb2.GenericResponse()
#
# generic_response.ParseFromString(response_bytes)
# # print(f"Response: {generic_response}")
#
# # reponseBytes =
def main():
# encoder_read_positions_request = CompLib_pb2.EncoderReadPositionsRequest()
# # readSensorsRequest.header = CompLib_pb2.Header()
# encoder_read_positions_request.header.message_type = encoder_read_positions_request.DESCRIPTOR.full_name
#
# start_time = time.time()
# for i in range(100000):
# send(encoder_read_positions_request.SerializeToString(), encoder_read_positions_request.ByteSize())
# print("--- %s seconds ---" % (time.time() - start_time))
from compLib.IRSensor import IRSensor
IRSensor.enable()
startTime = time.time()
while time.time() - startTime < 10:
print(IRSensor.read_all())
time.sleep(0.01)
# from compLib.Encoder import Encoder
# print(Encoder.read_all_positions())
# print(Encoder.read_all_velocities())
# from compLib.Motor import Motor
# Motor.speed(0, -50)
# Motor.speed(3, 50)
# Motor.power(0, -50)
# Motor.power(3, 50)
# time.sleep(5)
#
# import time
# time.sleep(2)
#
# Motor.speed(0, 0)
# Motor.speed(3, -0)
# Motor.power(0, 0)
# Motor.power(3, 0)
# import math
# from compLib.Movement import Movement
# Movement.turn_degrees(90, math.pi * 2)
# Movement.turn_degrees(-90, math.pi * 2)
#
# Movement.turn_degrees(90, math.pi * 2)
# Movement.turn_degrees(90, -math.pi * 2)
#
# Movement.turn_degrees(90, math.pi * 2)
# Movement.turn_degrees(-90, -math.pi * 2)
# from compLib.Movement import Movement
# Movement.drive_distance(0.1, 0.5)
# Movement.drive_distance(-0.1, 0.5)
#
# Movement.drive_distance(0.1, 0.5)
# Movement.drive_distance(0.1, -0.5)
#
# Movement.drive_distance(0.1, 0.5)
# Movement.drive_distance(-0.1, -0.5)
# from compLib.Movement import Movement
# import math
# import time
# Movement.drive_distance(0.5, 0.5)
# time.sleep(1)
# Movement.turn_degrees(90, math.pi * 2)
# time.sleep(1)
#
# Movement.drive_distance(0.5, 0.5)
# time.sleep(1)
# Movement.turn_degrees(90, math.pi * 2)
# time.sleep(1)
#
# Movement.drive_distance(0.5, 0.5)
# time.sleep(1)
# Movement.turn_degrees(90, math.pi * 2)
# time.sleep(1)
#
# Movement.drive_distance(0.5, 0.5)
# time.sleep(1)
# Movement.turn_degrees(90, math.pi * 2)
# time.sleep(1)
# import time
#
# from compLib.IRSensor import IRSensor
# from compLib.Motor import Motor
#
# IRSensor.enable()
#
# DRIVE_SPEED = 2.0
# COLOR_BREAK = 900
# KP = 0.25
# KD = 0.0
#
#
# def drive(leftSpeed, rightSpeed):
# Motor.speed(0, -rightSpeed)
# Motor.power(3, leftSpeed)
#
#
# def follow(sleepTime=0.1):
# lastError = 0
# sensorsBlack = 0
#
# while sensorsBlack < 3:
# data = IRSensor.read_all()
#
# sensorsBlack = 0
# for i in range(len(data)):
# if data[i] > COLOR_BREAK:
# sensorsBlack += 1
#
# error = lastError
# if data[2] > COLOR_BREAK:
# error = 0
# elif data[0] > COLOR_BREAK:
# error = -1.5
# elif data[4] > COLOR_BREAK:
# error = 1.5
# elif data[1] > COLOR_BREAK:
# error = -1
# elif data[3] > COLOR_BREAK:
# error = 1
# elif error == 1.5:
# error = 3
# elif error == -1.5:
# error = -3
#
# lastError = error
#
# adjustment = KP * error + KD * (error - lastError)
# leftSpeed = DRIVE_SPEED + adjustment
# rightSpeed = DRIVE_SPEED - adjustment
#
# print(f"{leftSpeed} {rightSpeed} {adjustment} {error}")
# drive(leftSpeed, rightSpeed)
#
# drive(0, 0)
# time.sleep(sleepTime)
if __name__ == '__main__':
CompLibClient.use_tcp_socket("dev03.local")
# follow()
main()

View file

@ -5,7 +5,7 @@ from typing import Dict, Tuple, List
import requests
logger = logging.getLogger("seeding-api")
logger = logging.getLogger("complib-logger")
API_URL = os.getenv("API_URL", "http://localhost:5000/") + "api/"
CONF_URL = os.getenv("API_URL", "http://localhost:5000/") + "config/"
@ -23,14 +23,14 @@ API_URL_GET_ROBOT_STATE = API_URL + "getRobotState"
class Seeding:
"""Class used for communicating with seeding api
"""Klasse welche mit der Seeding API Kommuniziert.
"""
@staticmethod
def get_heuballen() -> int:
"""Makes the /api/getHeuballen call to the api.
"""Macht den /api/getHeuballen request zur Seeding API.
:return: hueballencode as int.
:return: hueballencode als int.
:rtype: int
"""
res = requests.get(API_URL_GET_HEU)
@ -40,9 +40,9 @@ class Seeding:
@staticmethod
def get_logistic_plan() -> List:
"""Makes the /api/getLogisticPlan call to the api.
"""Macht den /api/getLogisticPlan zur Seeding API.
:return: Json Object and status code as returned by the api.
:return: Liste an logistic-centern, welche vom roboter in genau der Reihenfolge beliefert werden sollten.
:rtype: List
"""
res = requests.get(API_URL_GET_LOGISTIC_PLAN)
@ -52,7 +52,7 @@ class Seeding:
@staticmethod
def get_material_deliveries() -> List:
"""Makes the /api/getMaterialDeliveries call to the api.
"""Macht den /api/getMaterialDeliveries zur Seeding API.
:return: Json Object and status code as returned by the api.
:rtype: List

55
compLib/CMakeLists.txt Normal file
View file

@ -0,0 +1,55 @@
cmake_minimum_required(VERSION 3.26)
project(comp_lib)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rclcpp_action REQUIRED)
find_package(std_msgs REQUIRED)
find_package(irobot_create_msgs REQUIRED)
find_package(geometry_msgs REQUIRED)
# add_executable(talker src/publisher_member_function.cpp)
# ament_target_dependencies(talker rclcpp std_msgs irobot_create_msgs)
# add_executable(listener src/subscriber_member_function.cpp)
# ament_target_dependencies(listener rclcpp std_msgs irobot_create_msgs)
# add_executable(drive src/drive_action.cpp)
# ament_target_dependencies(drive rclcpp rclcpp_action std_msgs irobot_create_msgs)
# add_executable(set_vel src/set_speed.cpp)
# ament_target_dependencies(set_vel rclcpp rclcpp_action std_msgs irobot_create_msgs geometry_msgs)
add_executable(compLib_node src/main.cpp src/motor.cpp src/controls.cpp)
ament_target_dependencies(compLib_node rclcpp rclcpp_action std_msgs irobot_create_msgs geometry_msgs)
target_include_directories(compLib_node PRIVATE include)
install(TARGETS
#talker
#listener
#drive
#set_vel
compLib_node
DESTINATION lib/${PROJECT_NAME})
# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# uncomment the line when a copyright and license is not present in all source files
#set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# uncomment the line when this package is not in a git repo
#set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
ament_package()

231
compLib/Camera.py Normal file
View file

@ -0,0 +1,231 @@
import sys
from typing import Any, Tuple, List
# build image is somehow different from raspberry image? opencv-python is installed to a directory which is not in the pythonpath by default....
sys.path.append("/usr/lib/python3.9/site-packages")
import logging
import os
import queue
import threading
import cv2
from flask import Flask, Response
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)
SERVE_VIDEO = os.getenv("SERVER_SRC", "/live")
BUILDING_DOCS = os.getenv("BUILDING_DOCS", "false")
HTML = """
<html>
<head>
<title>Opencv Output</title>
</head>
<body>
<h1>Opencv Output</h1>
<img src="{{ VIDEO_DST }}">
</body>
</html>
"""
# it would be better to use jinja2 here, but I don't want to blow up the package dependencies...
HTML = HTML.replace("{{ VIDEO_DST }}", SERVE_VIDEO)
class Marker:
def __init__(self, id: int, x: float, y: float):
self.id: int = id
self.x: float = x
self.y: float = y
def __str__(self) -> str:
return f"Marker ID: {self.id}, position: {self.x} x, {self.y} y"
def __repr__(self) -> str:
return str({"id": self.id, "x": self.x, "y": self.y})
class Camera:
class __Webserver:
def __init__(self, camera):
self.app = Flask(__name__)
self.__camera = camera
self.__thread = threading.Thread(target=self.__start_flask, daemon=True)
self.__thread.start()
@self.app.route("/live")
def __video_feed():
"""
Define route for serving jpeg stream.
:return: Return the response generated along with the specific media.
"""
return Response(self.__camera._newest_frame_generator(),
mimetype="multipart/x-mixed-replace; boundary=frame")
@self.app.route("/")
def __index():
"""
Define route for serving a static http site to view the stream.
:return: Static html page where the video stream of Opencv can be viewed.
"""
return HTML
def __start_flask(self):
"""
Function for running flask server in a thread.
:return:
"""
logging.getLogger("complib-logger").info("starting flask server")
self.app.run(host="0.0.0.0", port=9898, debug=True, threaded=True, use_reloader=False)
class __NoBufferVideoCapture:
def __init__(self, cam):
self.cap = cv2.VideoCapture(cam)
self.cap.set(3, 640)
self.cap.set(4, 480)
self.q = queue.Queue(maxsize=3)
self.stopped = False
self.t = threading.Thread(target=self._reader, daemon=True)
self.t.start()
def _reader(self):
while not self.stopped:
ret, frame = self.cap.read()
if not ret:
continue
if self.q.full():
try:
self.q.get_nowait()
except queue.Empty:
pass
self.q.put(frame)
def read(self):
return self.q.get()
def stop(self):
self.stopped = True
self.t.join()
def __init__(self):
self.__logger = logging.getLogger("complib-logger")
self.__logger.info("capturing rtmp stream is disabled in this version")
self.__camera_stream = self.__NoBufferVideoCapture(-1)
self.__newest_frame = None
self.__lock = threading.Lock()
self.__webserver = self.__Webserver(self)
self.aruco_dict = cv2.aruco.Dictionary_get(cv2.aruco.DICT_6X6_50)
self.aruco_params = cv2.aruco.DetectorParameters_create()
self.__logger.info("Initialized vision")
def get_frame(self) -> Any:
"""
Die Funktion gibt das neuste Bild, welches die Kamera aufgenommen, hat zurück.
:return: Ein "opencv image frame"
"""
img16 = self.__camera_stream.read()
return img16
def detect_markers(self, image) -> Any:
"""
Funktion um die ArUco Marker in einem Bild zu erkennen.
:param image: Bild, welches die Kamera aufgenommen hat.
:return: Gibt drei Variablen zurueck. Erstens eine Liste an Postionen der "Ecken" der erkannten Markern. Zweitens eine Liste an IDs der erkannten Markern und dritten noch Debug Informationen (diese können ignoriert werden).
"""
return cv2.aruco.detectMarkers(image, self.aruco_dict, parameters=self.aruco_params)
def detect_markers_midpoint(self, image) -> Tuple[List[Marker], Any]:
"""
Funktion um die ArUco Marker in einem Bild zu erkennen, einzuzeichnen und den Mittelpunkt der Marker auszurechnen.
:param image: Bild, welches die Kamera aufgenommen hat.
:return: Gibt zwei Variablen zurueck. Erstens eine Liste an "Markern" und zweitens das Bild mit den eigezeichneten Marken.
:rtype: Tuple[List[Marker], Any]
"""
(corners, ids, rejected) = self.detect_markers(image)
self.draw_markers(image, corners, ids)
res = []
for i in range(0, len(corners)):
x = sum([point[0] for point in corners[i][0]]) / 4
y = sum([point[1] for point in corners[i][0]]) / 4
res.append(Marker(ids[i][0], x, y))
return res, image
def draw_markers(self, image, corners, ids) -> Any:
"""
Zeichnet die erkannten Markern mit ihren IDs in das Bild.
:param image: Original Bild, in dem die Marker erkannt wurden.
:param corners: List der Positionen der Ecken der erkannten Marker.
:param ids: IDs der erkannten Markern.
:return: Neues Bild mit den eigezeichneten Markern.
"""
return cv2.aruco.drawDetectedMarkers(image, corners, ids)
def publish_frame(self, image):
"""
Sendet das Bild, welches der Funktion übergeben wird, an den Webserver, damit es der Nutzer in seinem Browser ansehen kann.
:param image: Opencv Bild, welches dem Nutzer angezeigt werden soll.
:return: None
"""
with self.__lock:
if image is not None:
self.__newest_frame = image.copy()
def _newest_frame_generator(self):
"""
Private generator which is called directly from flask server.
:return: Yields image/jpeg encoded frames published from publish_frame function.
"""
while True:
# use a buffer frame to copy the newest frame with lock and then freeing it immediately
buffer_frame = None
with self.__lock:
if self.__newest_frame is None:
continue
buffer_frame = self.__newest_frame.copy()
# encode frame for jpeg stream
(flag, encoded_image) = cv2.imencode(".jpg", buffer_frame)
# if there was an error try again with the next frame
if not flag:
continue
# else yield encoded frame with mimetype image/jpeg
yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' +
bytearray(encoded_image) + b'\r\n')
# for debugging and testing start processing frames and detecting a 6 by 9 calibration chessboard
if __name__ == '__main__' and BUILDING_DOCS == "false":
camera = Camera()
while True:
image = camera.get_frame()
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
# processing
# gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# find the chessboard corners
# ret, corners = cv2.findChessboardCorners(gray, (6, 9), None)
# cv2.drawChessboardCorners(frame, (6, 9), corners, ret)
markers, image = camera.detect_markers_midpoint(image)
print(markers)
print("-----------------")
camera.publish_frame(image)

View file

@ -3,16 +3,10 @@ import os
import time
from typing import Tuple, List, Dict
import requests as requests
import requests
import logging
logger = logging.getLogger("seeding-api")
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
ch.setFormatter(formatter)
logger.addHandler(ch)
logger = logging.getLogger("complib-logger")
RETRY_TIMEOUT = 0.05
@ -22,7 +16,7 @@ API_URL = os.getenv("API_URL", "http://localhost:5000/") + "api/"
api_override = os.getenv("API_FORCE", "")
if api_override != "":
print(f"API_URL was set to {API_URL} but was overwritten with {api_override}")
logger.warning(f"API_URL was set to {API_URL} but was overwritten with {api_override}")
API_URL = api_override
API_URL_GET_ROBOT_STATE = API_URL + "getRobotState"
@ -35,7 +29,12 @@ API_URL_GET_SCORES = API_URL + "getScores"
class Position:
"""Datastructure for holding a position
"""
Datenstruktur, welche eine Position representiert.
:ivar x: X Position in Centimeter
:ivar y: Y Position in Centimeter
:ivar degrees: Rotation in Grad von -180 bis 180
"""
def __init__(self, x, y, degrees):
@ -63,13 +62,14 @@ class Position:
class DoubleElim:
"""Class used for communicating with double elimination api
"""Klasse für die Kommunikation mit Double Elimination Api
"""
@staticmethod
def get_pos() -> Tuple[Position, int]:
"""Makes the /api/getPos call to the api.
:return: A Position object with robot position
"""Führt den /api/getPos Aufruf an die API aus.
:return: Ein Objekt der Klasse :class:`.Position` mit der Position des Roboters und der Status Code
:rtype: Tuple[Position, int]
"""
res = requests.get(API_URL_GET_POS)
@ -86,8 +86,9 @@ class DoubleElim:
@staticmethod
def get_opponent() -> Tuple[Position, int]:
"""Makes the /api/getOp call to the api.
:return: A Position object with opponents robot position
"""Führt den /api/getOp Aufruf an die API aus.
:return: Ein Objekt der Klasse :class:`.Position` mit der Position des gegnerischen Roboters relativ zum eigenen Roboter und der Status Code
:rtype: Tuple[Position, int]
"""
res = requests.get(API_URL_GET_OP)
@ -104,8 +105,9 @@ class DoubleElim:
@staticmethod
def get_goal() -> Tuple[Position, int]:
"""Makes the /api/getGoal call to the api.
:return: A Position object with x and y coordinates of the goal, rotation is always -1
"""Führt den /api/getGoal Aufruf an die API aus.
:return: Ein Objekt der Klasse :class:`.Position` mit der Position des Ziels relativ zum eigenen Roboter und der Status Code
:rtype: Tuple[Position, int]
"""
res = requests.get(API_URL_GET_GOAL)
@ -122,8 +124,9 @@ class DoubleElim:
@staticmethod
def get_items() -> Tuple[List[Dict], int]:
"""Makes the /api/getItems call to the api.
:return: A list will all items currently on the game field. Items are dictionaries that look like: {"id": 0, "x": 0, "y": 0}
"""Führt den /api/getItems Aufruf an die API aus.
:return: Eine Liste aller Items, die sich derzeit auf dem Spielfeld befinden. Items sind "dictionaries", die wie folgt aussehen: {"id": 0, "x": 0, "y": 0}
:rtype: Tuple[List[Dict], int]
"""
res = requests.get(API_URL_GET_ITEMS)
@ -140,8 +143,9 @@ class DoubleElim:
@staticmethod
def get_scores() -> Tuple[Dict, int]:
"""Makes the /api/getScores call to the api.
:return: A dictionary with all scores included like: {"self":2,"opponent":0}
"""Führt den /api/getScores Aufruf an die API aus.
:return: Ein "dictionary" mit dem eignen Score und dem des Gegners: {"self":2,"opponent":0}
:rtype: Tuple[Dict, int]
"""
res = requests.get(API_URL_GET_SCORES)

View file

@ -3,33 +3,35 @@ from compLib.CompLibClient import CompLibClient
class Encoder(object):
"""Class used to read the encoders
"""Klasse zum Zugriff auf die Encoder der einzelnen Motoren
"""
@staticmethod
def read_all_positions():
"""Read all encoder positions.
"""Lesen aller absoluten Positionen der einzelnen Encoder
:return: Tuple of all current encoder positions
:return: Tupel mit allen aktuellen Encoderpositionen
"""
request = CompLib_pb2.EncoderReadPositionsRequest()
request.header.message_type = request.DESCRIPTOR.full_name
response = CompLib_pb2.EncoderReadPositionsResponse()
response.ParseFromString(CompLibClient.send(request.SerializeToString(), request.ByteSize()))
response.ParseFromString(CompLibClient.send(
request.SerializeToString(), request.ByteSize()))
return tuple(i for i in response.positions)
@staticmethod
def read_all_velocities():
"""Read the velocity of all motors connected.
"""Lesen der Geschwindigkeit aller angeschlossenen Motoren.
:return: Tuple of all current motor velocities
:return: Tupel aller aktuellen Motorgeschwindigkeiten in Radianten pro Sekunde
"""
request = CompLib_pb2.EncoderReadVelocitiesRequest()
request.header.message_type = request.DESCRIPTOR.full_name
response = CompLib_pb2.EncoderReadVelocitiesResponse()
response.ParseFromString(CompLibClient.send(request.SerializeToString(), request.ByteSize()))
response.ParseFromString(CompLibClient.send(
request.SerializeToString(), request.ByteSize()))
return tuple(i for i in response.velocities)

View file

@ -6,14 +6,14 @@ from compLib.CompLibClient import CompLibClient
class IRSensor(object):
"""Access the different IR Sensors of the robot
"""Ermöglicht den Zugriff auf die einzelnen IRSensoren des Roboters
"""
@staticmethod
def read_all():
"""Read all IR sensors at once.
"""Auslesen aller Sensoren gleichzeitig
:return: Array of all current ir sensors
:return: Array aller Sensorwerte
"""
request = CompLib_pb2.IRSensorsReadAllRequest()
request.header.message_type = request.DESCRIPTOR.full_name
@ -26,7 +26,7 @@ class IRSensor(object):
@staticmethod
def enable():
"""Turn on all IR emitters
"""Aktivieren Infrarot-Sender. Muss bei jedem Programmstart ausgeführt werden.
"""
request = CompLib_pb2.IRSensorsEnableRequest()
request.header.message_type = request.DESCRIPTOR.full_name
@ -36,7 +36,7 @@ class IRSensor(object):
@staticmethod
def disable():
"""Turn off all IR emitters
"""Deaktivieren der Infrarot-Sender
"""
request = CompLib_pb2.IRSensorsDisableRequest()
request.header.message_type = request.DESCRIPTOR.full_name

View file

@ -12,7 +12,7 @@ class Motor(object):
def power(port: int, percent: float):
"""Motor auf eine prozentuale Leistung der Höchstgeschwindigkeit einstellen
:param port: Port, an welchen der Motor angestecht wird. 0-3
:param port: Port, an welchen der Motor angesteckt ist. 0-3
:param percent: Prozentsatz der Höchstgeschwindigkeit. zwischen -100 und 100
:raises: IndexError
"""
@ -61,8 +61,8 @@ class Motor(object):
def speed(port: int, speed: float):
"""Geschwindigkeit des Motors einstellen
:param port: Port, an welchen der Motor angestecht wird. 0-3
:param speed: Drehzahl, mit der sich ein Motor dreht, in Radianten pro Sekunde (rad/s)
:param port: Port, an welchen der Motor angesteckt ist. 0-3
:param speed: Drehzahl, mit der sich ein Motor dreht, in Centimeter pro Sekunde (cm/s)
:raises: IndexError
"""
@ -103,7 +103,7 @@ class Motor(object):
def pulse_width(port: int, percent: float):
"""Setzen den Pulsbreite eines Motors in Prozent der Periode
:param port: Port, an welchen der Motor angestecht wird. 0-3
:param port: Port, an welchen der Motor angesteckt ist. 0-3
:param percent: Prozent der Periode zwischen -100 und 100
:raises: IndexError
"""
@ -120,7 +120,7 @@ class Motor(object):
@staticmethod
def multiple_pulse_width(*arguments: tuple[int, float]):
"""Setzen den Pulsbreite mehreer Motoren in Prozent der Periode
"""Setzen den Pulsbreite mehrerer Motoren in Prozent der Periode
:param arguments: tuple von port, prozent
:raises: IndexError

View file

@ -1,17 +1,24 @@
import logging
import os
import numpy as np
# TODO: if set to competition mode, get the seed from the api
FORCE_SEED = int(os.getenv("FORCE_SEED", "-1"))
def set_random_seed(seed: int):
np.random.seed(seed)
def get_random_number(min: int, max: int):
return np.random.randint(256 ** 4, dtype='<u4', size=1)[0] % (max - min + 1) + min
logger = logging.getLogger("complib-logger")
class Gamestate:
@staticmethod
def __set_random_seed(seed: int):
logger.debug(f"Seeding seed to: {seed}")
np.random.seed(seed)
@staticmethod
def __get_random_number(min: int, max: int):
return np.random.randint(256 ** 4, dtype='<u4', size=1)[0] % (max - min + 1) + min
def __str__(self) -> str:
return f"""Seed: {self.seed}
Heu Color: {self.heu_color}
@ -21,18 +28,30 @@ Logistic Plan: {self.logistic_plan}
Logistic Centers: {self.logistic_center}"""
def __init__(self, seed: int):
self.seed = seed
set_random_seed(seed)
"""
Erstellt den Seeding "Gamestate" für den angegebenen Seed.
self.heu_color = get_random_number(1, 2)
:param seed: Seed welcher zum Erstellen des Gamestates benutzt werden soll.
"""
if FORCE_SEED == -1:
self.seed = seed
else:
print(f"Wettkampfmodus, zufälliger Seed wird verwendet: Seed={FORCE_SEED}")
self.seed = FORCE_SEED
logger.debug(f"Creating gamestate with seed: {self.seed}")
self.__set_random_seed(self.seed)
self.heu_color = self.__get_random_number(1, 2)
self.materials = [0, 0, 0, 0]
self.material_pairs = []
for i in range(0, 4):
num1 = get_random_number(0, 3)
num1 = self.__get_random_number(0, 3)
self.material_pairs.append([num1, num1])
while self.material_pairs[i][1] == num1:
self.material_pairs[i][1] = get_random_number(0, 3)
self.material_pairs[i][1] = self.__get_random_number(0, 3)
flat = [item for sublist in self.material_pairs for item in sublist]
for i in range(0, 4):
@ -43,7 +62,7 @@ Logistic Centers: {self.logistic_center}"""
visited = [5, 5, 5, 5]
def __logistic_plan_generator(i: int):
drive_to = get_random_number(0, 3)
drive_to = self.__get_random_number(0, 3)
for j in range(0, 4):
drive_to = (drive_to + j) % 4
if visited[drive_to] <= 0 or drive_to == self.logistic_plan[i - 1]:
@ -78,12 +97,28 @@ Logistic Centers: {self.logistic_center}"""
self.logistic_center[self.logistic_plan[i]][self.logistic_plan[i + 1]] += 1
self.logistic_plan = [x + 10 for x in self.logistic_plan]
logger.debug(f"Created gamesate: {str(self)}")
def get_heuballen(self) -> int:
"""
Die Funktion gibt entweder die Zahl "1" oder "2" zurück. Wenn die Funktion "1" zurückgibt, dann liegen die Heuballen auf den gelben Linien. Wenn die Funktion "2" zurückgibt, dann liegen sie auf den blauen Flächen.
:return: Gibt entweder die Zahl 1 oder 2 zurück.
"""
return self.heu_color
def get_logistic_plan(self) -> []:
"""
Die Funktion gibt den "Logistik Plan" zurück. Also die Reihenfolge, in welcher der Roboter die Logistik Zonen Abfahren muss, um die Pakete welche dort liegen zu sortieren.
:return: Eine Liste an Zahlen zwischen 10 und 13.
"""
return self.logistic_plan
def get_material_deliveries(self) -> [[]]:
"""
Die Funktion gibt die einzelnen "Material Lieferungen" zurück. Da der Roboter immer zwei Paare an Materialien anliefern muss, gibt die Funktion eine Liste an Material Paaren zurück. Die Materialien werden dabei durch ihre Zonen-ID representiert. Also Holz ist z.B. "0" und die Ziegelsteine sind "3".
:return: Eine Liste and Material Paaren.
"""
return self.material_pairs

7
compLib/__init__.py Normal file
View file

@ -0,0 +1,7 @@
import logging
import os
if os.getenv("DEBUG", "0") != "0":
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.DEBUG)
else:
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)

View file

@ -0,0 +1,14 @@
#ifndef ROS_NODE_H
#define ROS_NODE_H
#include "rclcpp/rclcpp.hpp"
class CompLibNode : public rclcpp::Node
{
public:
CompLibNode();
};
#endif

View file

@ -0,0 +1,24 @@
#ifndef CONTROLS_H
#define CONTROLS_H
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "irobot_create_msgs/msg/interface_buttons.hpp"
#include "irobot_create_msgs/msg/lightring_leds.hpp"
class ButtonPressNode : public rclcpp::Node
{
public:
ButtonPressNode();
void bt1_wait();
void bt2_wait();
void kill();
private:
void result_callback(const irobot_create_msgs::msg::InterfaceButtons::SharedPtr result);
rclcpp::Subscription<irobot_create_msgs::msg::InterfaceButtons>::SharedPtr interface_buttons_subscriber_;
bool button1{false};
bool button2{false};
};
#endif

67
compLib/include/motor.h Normal file
View file

@ -0,0 +1,67 @@
#ifndef MOTOR_H
#define MOTOR_H
#include <thread>
#include <memory>
#include <geometry_msgs/msg/twist.hpp>
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "irobot_create_msgs/action/drive_distance.hpp"
#include "irobot_create_msgs/action/drive_arc.hpp"
#include "irobot_create_msgs/action/rotate_angle.hpp"
class DriveDistNode : public rclcpp::Node
{
public:
DriveDistNode();
void drive_dist(float meters, float velocity);
void kill();
private:
void result_callback(const rclcpp_action::ClientGoalHandle<irobot_create_msgs::action::DriveDistance>::WrappedResult & result);
rclcpp_action::Client<irobot_create_msgs::action::DriveDistance>::SharedPtr drive_dist_action_;
bool processing;
};
class SetSpeedNode : public rclcpp::Node
{
public:
SetSpeedNode();
void drive(float speed);
void stop();
void kill();
private:
void set_speed(float speed);
void drive_loop(float speed);
rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr speed_publisher_;
bool run = true;
std::thread t;
};
class RotateAngleNode : public rclcpp::Node
{
public:
RotateAngleNode();
void rotate_angle(float angle, float velocity);
void kill();
private:
void result_callback(const rclcpp_action::ClientGoalHandle<irobot_create_msgs::action::RotateAngle>::WrappedResult & result);
rclcpp_action::Client<irobot_create_msgs::action::RotateAngle>::SharedPtr rotate_angle_action_;
bool processing;
};
class DriveArcNode : public rclcpp::Node
{
public:
DriveArcNode();
void drive_arc(float angle, float radius, float velocity, int direction=1);
void kill();
private:
void result_callback(const rclcpp_action::ClientGoalHandle<irobot_create_msgs::action::DriveArc>::WrappedResult & result);
rclcpp_action::Client<irobot_create_msgs::action::DriveArc>::SharedPtr drive_arc_action_;
bool processing;
};
#endif

View file

@ -0,0 +1,11 @@
#ifndef SEQUENCE_LOCK_H
#define SEQUENCE_LOCK_H
#include <mutex>
namespace SequenceLock
{
std::mutex m;
}
#endif

22
compLib/package.xml Normal file
View file

@ -0,0 +1,22 @@
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>comp_lib</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="matthias@todo.todo">matthias</maintainer>
<license>TODO: License declaration</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<depend>rclcpp</depend>
<depend>rclcpp_action</depend>
<depend>std_msgs</depend>
<depend>irobot_create_msgs</depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>

View file

@ -0,0 +1,10 @@
#include "ros_node.h"
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
CompLibNode::CompLibNode()
: Node("CompLibNode")
{
}

53
compLib/src/controls.cpp Normal file
View file

@ -0,0 +1,53 @@
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "irobot_create_msgs/msg/interface_buttons.hpp"
#include "controls.h"
ButtonPressNode::ButtonPressNode()
: Node("button_press_node")
{
interface_buttons_subscriber_ = this->create_subscription<irobot_create_msgs::msg::InterfaceButtons>(
"/interface_buttons",
rclcpp::SensorDataQoS(),
std::bind(&ButtonPressNode::result_callback, this, std::placeholders::_1)
);
}
void ButtonPressNode::bt1_wait()
{
RCLCPP_INFO(this->get_logger(), "Wait for button 1...");
button1 = false;
while (!button1) {}
button1 = false;
}
void ButtonPressNode::bt2_wait()
{
RCLCPP_INFO(this->get_logger(), "Wait for button 2...");
button2 = false;
while (!button2) {}
button2 = false;
}
void ButtonPressNode::result_callback(const irobot_create_msgs::msg::InterfaceButtons::SharedPtr result)
{
if (result->button_1.is_pressed) {
button1 = true;
}
if (result->button_2.is_pressed) {
button2 = true;
}
if (result->button_power.is_pressed) {
}
}
void ButtonPressNode::kill()
{
RCLCPP_INFO(this->get_logger(), "ButtonPressNode killed");
rclcpp::shutdown();
}

93
compLib/src/main.cpp Normal file
View file

@ -0,0 +1,93 @@
#include <thread>
#include <memory>
#include <chrono>
#include <mutex>
#include "motor.h"
#include "controls.h"
std::mutex action_mutex;
// #lazyness
void run_node(std::shared_ptr<DriveDistNode> node)
{
rclcpp::spin(node);
}
void run_node1(std::shared_ptr<SetSpeedNode> node)
{
rclcpp::spin(node);
}
void run_node2(std::shared_ptr<RotateAngleNode> node)
{
rclcpp::spin(node);
}
void run_node3(std::shared_ptr<DriveArcNode> node)
{
rclcpp::spin(node);
}
void run_node4(std::shared_ptr<ButtonPressNode> node)
{
rclcpp::spin(node);
}
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
auto ddn = std::make_shared<DriveDistNode>();
auto ssn = std::make_shared<SetSpeedNode>();
auto ran = std::make_shared<RotateAngleNode>();
auto dan = std::make_shared<DriveArcNode>();
auto bpn = std::make_shared<ButtonPressNode>();
std::thread t;
std::thread t1;
std::thread t2;
std::thread t3;
std::thread t4;
t = std::thread(run_node, ddn);
t1 = std::thread(run_node1, ssn);
t2 = std::thread(run_node2, ran);
t3 = std::thread(run_node3, dan);
t4 = std::thread(run_node4, bpn);
bpn->bt1_wait();
bpn->bt2_wait();
bpn->bt1_wait();
ssn->drive(0.3);
std::this_thread::sleep_for (std::chrono::milliseconds(2000));
ssn->stop();
// std::this_thread::sleep_for (std::chrono::milliseconds(1000));
ran->rotate_angle(-45, 0.5);
// std::this_thread::sleep_for (std::chrono::milliseconds(5000));
ddn->drive_dist(0.2, 0.3);
// std::this_thread::sleep_for (std::chrono::milliseconds(5000));
dan->drive_arc(90, 0.5, 0.5);
ddn->kill();
ssn->kill();
ddn->kill();
dan->kill();
bpn->kill();
t.join();
t1.join();
t2.join();
t3.join();
t4.join();
return 0;
}

225
compLib/src/motor.cpp Normal file
View file

@ -0,0 +1,225 @@
#include <chrono>
#include <thread>
#include <mutex>
#include <future>
#include <memory>
#include <geometry_msgs/msg/twist.hpp>
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "irobot_create_msgs/action/drive_distance.hpp"
#include "irobot_create_msgs/action/drive_arc.hpp"
#include "irobot_create_msgs/action/rotate_angle.hpp"
#include "motor.h"
#include "sequence_lock.h"
double pi = 2 * acos(0.0);
DriveDistNode::DriveDistNode()
: Node("drive_dist_node")
{
drive_dist_action_ = rclcpp_action::create_client<irobot_create_msgs::action::DriveDistance>(
this,
"/drive_distance"
);
}
void DriveDistNode::drive_dist(float meters, float velocity)
{
RCLCPP_INFO(this->get_logger(), "drive dist");
processing = true;
auto data = irobot_create_msgs::action::DriveDistance::Goal();
auto send_goal_options = rclcpp_action::Client<irobot_create_msgs::action::DriveDistance>::SendGoalOptions();
send_goal_options.result_callback =
std::bind(&DriveDistNode::result_callback, this, std::placeholders::_1);
data.distance = meters;
data.max_translation_speed = velocity;
drive_dist_action_->async_send_goal(data, send_goal_options);
while (processing) {}
}
void DriveDistNode::result_callback(const rclcpp_action::ClientGoalHandle<irobot_create_msgs::action::DriveDistance>::WrappedResult & result)
{
processing = false;
switch (result.code) {
case rclcpp_action::ResultCode::SUCCEEDED:
RCLCPP_INFO(this->get_logger(), "finished dist");
return;
case rclcpp_action::ResultCode::ABORTED:
RCLCPP_ERROR(this->get_logger(), "Goal was aborted");
return;
case rclcpp_action::ResultCode::CANCELED:
RCLCPP_ERROR(this->get_logger(), "Goal was canceled");
return;
default:
RCLCPP_ERROR(this->get_logger(), "Unknown result code");
return;
}
}
void DriveDistNode::kill()
{
RCLCPP_INFO(this->get_logger(), "DriveDistNode killed");
rclcpp::shutdown();
}
SetSpeedNode::SetSpeedNode()
: Node("set_speed_node")
{
speed_publisher_ = this->create_publisher<geometry_msgs::msg::Twist>(
"/cmd_vel",
rclcpp::SensorDataQoS()
);
}
void SetSpeedNode::drive_loop(float speed)
{
while (run)
{
set_speed(speed);
// sleep set as described at http://wiki.ros.org/Robots/TIAGo/Tutorials/motions/cmd_vel
std::this_thread::sleep_for (std::chrono::milliseconds(333));
}
}
void SetSpeedNode::set_speed(float speed)
{
auto data = geometry_msgs::msg::Twist();
data.linear.x = speed;
data.linear.y = 0;
data.linear.z = 0;
data.angular.x = 0;
data.angular.x = 0;
data.angular.x = 0;
speed_publisher_->publish(data);
}
void SetSpeedNode::drive(float speed)
{
RCLCPP_INFO(this->get_logger(), "Start drive");
run = true;
t = std::thread(&SetSpeedNode::drive_loop, this, speed);
}
void SetSpeedNode::stop()
{
run = false;
RCLCPP_INFO(this->get_logger(), "Stop drive");
}
void SetSpeedNode::kill()
{
RCLCPP_INFO(this->get_logger(), "SetSpeedNode killed");
rclcpp::shutdown();
}
RotateAngleNode::RotateAngleNode()
: Node("rotate_angle_node")
{
rotate_angle_action_ = rclcpp_action::create_client<irobot_create_msgs::action::RotateAngle>(
this,
"rotate_angle"
);
}
void RotateAngleNode::rotate_angle(float angle, float velocity)
{
processing = true;
angle *= pi / 180;
auto data = irobot_create_msgs::action::RotateAngle::Goal();
auto send_goal_options = rclcpp_action::Client<irobot_create_msgs::action::RotateAngle>::SendGoalOptions();
send_goal_options.result_callback =
std::bind(&RotateAngleNode::result_callback, this, std::placeholders::_1);
data.angle = angle;
data.max_rotation_speed = velocity;
rotate_angle_action_->async_send_goal(data, send_goal_options);
while (processing) {}
}
void RotateAngleNode::result_callback(const rclcpp_action::ClientGoalHandle<irobot_create_msgs::action::RotateAngle>::WrappedResult & result)
{
processing = false;
switch (result.code) {
case rclcpp_action::ResultCode::SUCCEEDED:
RCLCPP_INFO(this->get_logger(), "finished rotation");
return;
case rclcpp_action::ResultCode::ABORTED:
RCLCPP_ERROR(this->get_logger(), "Goal was aborted");
return;
case rclcpp_action::ResultCode::CANCELED:
RCLCPP_ERROR(this->get_logger(), "Goal was canceled");
return;
default:
RCLCPP_ERROR(this->get_logger(), "Unknown result code");
return;
}
}
void RotateAngleNode::kill()
{
RCLCPP_INFO(this->get_logger(), "RotateAngleNode killed");
rclcpp::shutdown();
}
DriveArcNode::DriveArcNode()
: Node("drive_arc_node")
{
drive_arc_action_ = rclcpp_action::create_client<irobot_create_msgs::action::DriveArc>(
this,
"/drive_arc"
);
}
void DriveArcNode::drive_arc(float angle, float radius, float velocity, int direction)
{
processing = true;
angle *= pi / 180;
auto data = irobot_create_msgs::action::DriveArc::Goal();
auto send_goal_options = rclcpp_action::Client<irobot_create_msgs::action::DriveArc>::SendGoalOptions();
send_goal_options.result_callback =
std::bind(&DriveArcNode::result_callback, this, std::placeholders::_1);
data.angle = angle;
data.radius = radius;
data.translate_direction = direction;
data.max_translation_speed = velocity;
drive_arc_action_->async_send_goal(data, send_goal_options);
while (processing) {}
}
void DriveArcNode::result_callback(const rclcpp_action::ClientGoalHandle<irobot_create_msgs::action::DriveArc>::WrappedResult & result)
{
processing = false;
switch (result.code) {
case rclcpp_action::ResultCode::SUCCEEDED:
RCLCPP_INFO(this->get_logger(), "finished arc");
return;
case rclcpp_action::ResultCode::ABORTED:
RCLCPP_ERROR(this->get_logger(), "Goal was aborted");
return;
case rclcpp_action::ResultCode::CANCELED:
RCLCPP_ERROR(this->get_logger(), "Goal was canceled");
return;
default:
RCLCPP_ERROR(this->get_logger(), "Unknown result code");
return;
}
}
void DriveArcNode::kill()
{
RCLCPP_INFO(this->get_logger(), "DriveArcNode killed");
rclcpp::shutdown();
}

View file

@ -22,6 +22,7 @@ os.environ["EXTENSIVE_LOGGING"] = "False"
project = 'CompLib'
copyright = '2022, Verein zur Förderung von Wissenschaft und Technik an Schulen (F-WuTS)'
author = 'robo4you'
autoclass_content = 'both'
# The full version, including alpha/beta/rc tags
release = '0.2.3'
@ -59,5 +60,10 @@ html_theme = 'sphinx_rtd_theme'
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
html_logo = "images/compair-logo-white.svg"
html_theme_options = {
'logo_only': True,
'display_version': False,
}
language = "de"

32
docs/source/faq.rst Normal file
View file

@ -0,0 +1,32 @@
FAQ
###
Was ist das Passwort für die Entwicklungsumgebung?
--------------------------------------------------
``compair``
Wie verbinde ich mich zur Entwicklungsumgebung?
-----------------------------------------------
See :ref:`gettingstarted_codeserver`
Was ist der Benutzername und das Passwort für den Raspberry Pi?
---------------------------------------------------------------
``compair`` ``compair``
Wie aktualisiere ich meine Software?
------------------------------------
.. code-block:: bash
sudo apt update
sudo apt upgrade
sudo update-firmware
Wie kann ich die SD-Karte neu beschreiben?
------------------------------------------
`SD-Karten Image <https://drive.google.com/drive/folders/16lMe-yGphk947L4WPjd4oD8ndY9R1WbA?usp=share_link>`_
Software zum Schreiben der SD-Karte `balenaEtcher <https://www.balena.io/etcher/>`_

View file

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 239 KiB

After

Width:  |  Height:  |  Size: 239 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

View file

@ -7,5 +7,6 @@ Erste Schritte
wifi.rst
codeServer.rst
firstProgram.rst
update.rst
secondProgram.rst
thridProgram.rst

View file

@ -0,0 +1,13 @@
Software Updaten
#################
Da wir die ``compLib``, und die andere Software, welche auf dem Roboter läuft, laufend weiterentwickeln, solltet ihr immer wieder euren Roboter auf die neuste Version updaten. Dazu müsst ihr einfach den Roboter mit dem Internet verbinden und dann diesen Befehl in der Kommandozeile des Roboters eingeben:
.. code-block:: bash
sudo apt update && sudo apt upgrade
Am einfachsten kann das über die Webseite gemacht werden, auf der ihr auch euren Code schreibt. Dazu müsst ihr einfach nur das Terminal (= Konsole) öffnen, dann den Befehl dort hineinkopieren und Enter drücken.
|updatePic|
.. |updatePic| image:: images/09_update.png

View file

@ -33,6 +33,15 @@ Weitere Informationen
Die "wpa_supplicant.conf" Datei wird beim Start des Rpasberry Pi automatisch an den richtigen Ort kopiert, damit sich der Roboter zum Wlan verbindet.
Eine genauere Anleitung wird vom Hersteller des Raspberry Pi `hier <https://www.raspberrypi.com/documentation/computers/configuration.html#configuring-networking-2>`_ bereitgestellt.
Windows......
-------------
Je nach Betriebssystem und Editor, mit dem Sie die Datei erstellen, könnte die Datei falsche Zeilenumbrüche oder eine falsche Dateierweiterung haben; stellen Sie also sicher, dass Sie einen Editor verwenden, der dies berücksichtigt. Linux erwartet das Zeilenumbruchzeichen LF (Line Feed).
Beispielsweise kann `Notepad++ <https://notepad-plus-plus.org/downloads/>`_ verwendet werden, um die Datei richtig zu speichern.
|notepadImage|
.. |notepadImage| image:: images/08_notepad.png
Fehlerbehandlung
----------------
Sollte es dazu kommen, dass der Roboter nicht automatisch die Verbindung mit dem Netzwerk herstellt, kann eine Kabelgebundene Verbindung zur Diagnose von Fehlern genutzt werden.

View file

Before

Width:  |  Height:  |  Size: 188 KiB

After

Width:  |  Height:  |  Size: 188 KiB

Before After
Before After

View file

@ -1,5 +1,3 @@
.. image:: images/compair-logo-white.svg
Dokumentation des Roboters
##########################
@ -7,8 +5,8 @@ Dokumentation des Roboters
:maxdepth: 2
:caption: Contents:
Contents
*********
Inhalt
******
.. toctree::
:maxdepth: 1
@ -19,3 +17,4 @@ Contents
faq.rst
other/usage
lib/index.rst
other/hardware.rst

View file

@ -0,0 +1,13 @@
.. _lib_doubleElim:
Double Elimination
*******************
Dokumentation des Double Elimination Moduls
============================================
.. autoclass:: compLib.DoubleElimination.Position
:members:
.. autoclass:: compLib.DoubleElimination.DoubleElim
:members:

View file

@ -0,0 +1,10 @@
.. _lib_encoder:
Encoder
*******
Dokumentation der Klasse
========================
.. autoclass:: compLib.Encoder.Encoder
:members:

View file

@ -0,0 +1,10 @@
.. _lib_irsensor:
Infrarot Sensoren
*****************
Dokumentation der Klasse
========================
.. autoclass:: compLib.IRSensor.IRSensor
:members:

View file

@ -0,0 +1,79 @@
.. _lib_camera:
Camera und OpenCV
*******************
Dokumentation des Camera Moduls
================================
.. autoclass:: compLib.Camera.Marker
:members:
.. autoclass:: compLib.Camera.Camera
:members:
Beispiele
=========
Bild Anzeigen
---------------
Das folgende Programm fragt Bilder von der Kamera ab und schickt sie an den Webserver, der im Hintergrund läuft. Der Benutzer kann dann auf die Webseite: http://raspi_ip:9898 gehen und die Ausgabe der Kamera sehen.
.. code-block:: python
from compLib.Camera import *
camera = Camera()
while True:
image = camera.get_frame()
camera.publish_frame(image)
ArUco Marker Erkennen
------------------------
In diesem Programm werden die ArUco Marker, die sich am Spielfeld befinden, erkannt. Diese "QR-Code" ähnlichen Marker finden sich in den Logistikzonen und können dazu verwendet werden zu erkennen, wo der Roboter hinfahren sollt etc.
.. code-block:: python
from compLib.Camera import *
camera = Camera()
while True:
image = camera.get_frame()
markers, image = camera.detect_markers_midpoint(image)
print(markers)
print("-----------------")
camera.publish_frame(image)
Hier ist z.B. der ArUco Marker mit der ID 0. Führe das Programm aus und lass den Roboter auf den Bildschirm schauen. Das Programm sollte die 2D Position ausgeben, welcher der ArUco Marker (genauer sein Mittelpunkt) im Camera Bild hat.
|ArucoExample|
.. |ArucoExample| image:: images/6x6_1000-0.png
Um die Positionen zu verarbeiten, muss dann nur noch das "markers" array durchgegangen werden. Das könnte z.B. so gemacht werden:
.. code-block:: python
from compLib.Camera import *
camera = Camera()
while True:
image = camera.get_frame()
markers, image = camera.detect_markers_midpoint(image)
print(markers)
print("-----------------")
for marker in markers:
print(f"Marker mit der id: {marker.id}")
print(f"Ist auf der X Position: {marker.x}")
print(f"und auf der Y Position: {marker.y}")
print("-----------------")
camera.publish_frame(image)
Wichtig ist noch zu beachten, dass die X und Y Positionen ihren Ursprung in der linken oberen Ecke des Bildes haben. D.h. die Position (0,0) ist im oberen linken Bildrand.

View file

@ -0,0 +1,36 @@
.. _lib_seeding:
Seeding
*******
Dokumentation des Seeding Moduls
================================
.. autoclass:: compLib.Seeding.Gamestate
:members:
Beispiele
----------
| In ``Zeile 1`` wird das Seeding Modul importiert.
| In ``Zeile 2`` definieren wir dann eine Variable, in der wir den "Seed" des Gamestates den wir erstellen wollten speichern.
| In ``Zeile 3`` erstellen wir dann einen neuen Gamestate mit dem Seed und speichern ihn in die Variable ``gamestate``.
| In ``Zeile 4`` geben wir dann den Gamestate aus, damit wir ihn auf der Konsole ansehen können.
.. code-block:: python
import compLib.Seeding as Seeding
seed = 42
gamestate = Seeding.Gamestate(seed)
print(gamestate)
In der Ausgabe des Print Statements sehen wir den generierten Gamestate.
.. code-block::
Seed: 42
Heu Color: 1
Material Pairs: [[3, 0], [2, 3], [0, 2], [1, 2]]
Material Zones: [2, 1, 3, 2]
Logistic Plan: [12, 13, 12, 13, 10, 11, 13, 10, 13, 12, 11, 10, 11, 13, 10, 11, 12, 11, 12, 10, 12]
Logistic Centers: [[0, 3, 1, 1], [1, 0, 2, 2], [1, 2, 0, 2], [3, 0, 2, 0]]

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View file

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 391 KiB

After

Width:  |  Height:  |  Size: 391 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 356 KiB

After

Width:  |  Height:  |  Size: 356 KiB

Before After
Before After

12
docs/source/lib/index.rst Normal file
View file

@ -0,0 +1,12 @@
compLib
#######
.. toctree::
:maxdepth: 5
classes/Motor
classes/Encoder
classes/IRSensor
classes/Seeding
classes/DoubleElimination
classes/Opencv

View file

@ -0,0 +1,64 @@
.. _other_bardware:
Hardware
########
Sensorarray
***********
|SensorarrayImage|
.. |SensorarrayImage| image:: images/Sensorarray.png
Specs V4
--------
| **Processor:** `STM32G030F6P6 <https://mou.sr/3UxW49B>`_ - 32-bit ARM Cortex M0 CPU @ 64 MHz
| **I/O:** 1x I2C, 1x SWD
| **Sensors:** 5x `QRE1113GR <https://mou.sr/3TWGYdI>`_
Specs V2
--------
| **Processor:** `ATMEGA328P-AU <https://mou.sr/3FxhPC5>`_ - 8-bit CPU @ 16 MHz
| **I/O:** 1x I2C, 1x UART, 1x ISP
| **Sensors:** 5x `QRE1113GR <https://mou.sr/3TWGYdI>`_
Details
-------
Das Sensorarray wird verwendet um Linienen vor dem Roboter zu erkennen. Es agiert als I2C Slave und muss dementsprechend aktiv gepollt werden.
Zusätzlich besteht die möglichkeit alle Emitter zu deaktiviern um einen eventuellen Messfehler durch Sonneneinstralung oder andere Störquellen zu erkennen.
Version 4 unterscheidet sich zu Version 2 im Mikroprozessor, da es zu Lieferengpässen des ATMEGA gekommen ist.
Zusätzlich wurde die möglichkeit alle Emitter einzeln an bzw. auszuschalten entfernt, da diese keinen signifikanten Mehrwert brachte.
Motorboard
**********
|MainboardImage|
.. |MainboardImage| image:: images/Mainboard.png
Specs
-----
**Motor-Treiber:** `LV8548MC-AH <https://mou.sr/3TXbFzu>`_
Details
-------
Das Motorboard kann an einen der 4 Ports am Roboter angesteckt werden und ermöglicht das Ansteuern von Motoren und auslesen von Encodern.
Mainboard
*********
Specs
-----
| **Processor:** `STM32L051C8T6TR <https://mou.sr/3fuaAQv>`_ - 32-bit ARM Cortex M0 @ 32MHz
| **I/O:** 4x I2C (3x Bus 1, 1x Bus 2), 1x 40 Pin GPIO Header, 2x SPI (Verbunden mit GPIO), 4x Motor-/Servo-connector, 1x SWD, 1x USB-C
Details
-------
Das Mainboard wird auf den GPIO-Header eines Raspberry Pi gesteckt und ermöglicht die Steuerung eines Roboters mittels 4 Motor- bzw. Servo-Ports. Der RaspberryPi kommuniziert dabei mittels SPI mit dem Mainboard und steuert die einzelnen Sensoren oder Module an.
Zusätzlich befinden sich auf der Unterseite des Mainboards Lötstellen, welche direkt mit der Stromversorgung der Motoren verbunden sind und geben so die möglichkeit Motoren mit mehr als 5V anzusteuern.

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

166
docs/source/other/usage.rst Normal file
View file

@ -0,0 +1,166 @@
.. _other_usage:
Beispiele
#########
Vorwärts und rückwärts fahren
*****************************
.. code-block:: python
import time
from compLib.Motor import *
def forward():
Motor.power(0, -30)
Motor.power(3, 30)
def backward():
Motor.power(0, 30)
Motor.power(3, -30)
def main():
print("hallo ich bin ein roboter beep buup")
forward()
time.sleep(1)
backward()
time.sleep(1)
if __name__ == '__main__':
main()
Eine Linie verfolgen
********************
.. code-block:: python
import time
from compLib.Motor import Motor
from compLib.Encoder import Encoder
from compLib.IRSensor import IRSensor
COLOR_BREAK = 850
DRIVE_SPEED = 35
IRSensor.enable()
def drive(left, right):
right *= -1
Motor.multiple_power((0, right), (3, left))
print(f"{left} {right}")
def follow():
while True:
sensors = IRSensor.read_all()
if sensors[0] > COLOR_BREAK:
# turn left
drive(-DRIVE_SPEED, DRIVE_SPEED)
elif sensors[4] > COLOR_BREAK:
# turn right
drive(DRIVE_SPEED, -DRIVE_SPEED)
else:
# straight
drive(DRIVE_SPEED, DRIVE_SPEED)
if sensors[0] > COLOR_BREAK and sensors[4] > COLOR_BREAK:
break
drive(0, 0)
time.sleep(1)
def main():
follow()
drive(DRIVE_SPEED, DRIVE_SPEED)
time.sleep(0.5)
follow()
drive(DRIVE_SPEED, DRIVE_SPEED)
time.sleep(0.5)
follow()
drive(DRIVE_SPEED, DRIVE_SPEED)
time.sleep(0.5)
follow()
if __name__ == "__main__":
main()
Funktionalität des Roboters überprüfen
**************************************
.. code-block:: python
import time
from compLib.Motor import Motor
from compLib.Encoder import Encoder
from compLib.IRSensor import IRSensor
def testIR():
print("Enabling Infrared Sensor")
IRSensor.enable()
time.sleep(1)
print("Writing sensor values...")
for i in range(0, 50):
print(IRSensor.read_all())
time.sleep(0.1)
print("Disabling Infrared Sensor")
IRSensor.disable()
def testEncoders():
Motor.multiple_pulse_width((0, 50), (3, -50))
print("Writing encoder positions...")
for i in range(0, 50):
print(Encoder.read_all_positions())
time.sleep(0.1)
time.sleep(2)
print("Writing encoder velocities...")
for i in range(0, 50):
print(Encoder.read_all_velocities())
time.sleep(0.1)
Motor.multiple_pulse_width((0, 0), (3, 0))
def testMotors():
print("Setting pulse_with")
Motor.multiple_pulse_width((0, 50), (3, -50))
time.sleep(3)
print("Setting power")
Motor.multiple_power((0, 50), (3, -50))
time.sleep(3)
print("Setting pulse_with")
Motor.multiple_speed((0, 5), (3, -5))
time.sleep(3)
for i in range(0, 100):
Motor.multiple_power((0, i), (3, -i))
time.sleep(0.1)
if __name__ == "__main__":
print("Make sure robot is turned on it's back!")
time.sleep(5)
print()
print("----------------- Testing Infrared Sensor -----------------")
testIR()
print()
print("----------------- Testing Encoder -----------------")
testEncoders()
print()
print("----------------- Testing Motors -----------------")
testMotors()

View file

@ -0,0 +1,59 @@
.. _software_installation:
Installationsanweisungen
########################
Diese Anleitung dient dazu die Software auf dem Roboter neu aufzusetzen.
**Im normalen Gebraucht sollte dies jedoch nicht notwendig sein.**
Betriebssystem aufsetzen
========================
Als Basis wird für den Roboter Raspberry Pi OS (64-bit) verwendet. Das 32-Bit Betriebssystem wird nicht unterstützt, da die Software-Komponenten nur für aarch64 bzw. arm64/v8 kompiliert werden.
Genauere Informationen sind `hier <https://www.raspberrypi.com/software/operating-systems/>`_ zu finden.
Bearbeiten der boot-Partition
=============================
1. ``cmdline.txt``
::
console=tty1 root=PARTUUID=21e60f8c-02 rootfstype=ext4 fsck.repair=yes rootwait quiet init=/usr/lib/raspi-config/init_resize.sh
Stellen Sie sicher, dass die folgenden Einstellungen in der ``config.txt`` korrekt gesetzt sind
2. ``config.txt``
::
# SPI
dtparam=spi=on
dtoverlay=spi1-3cs
# Run in 64-bit mode
arm_64bit=1
[all]
[pi4]
# Run as fast as firmware / board allows
arm_boost=1
[all]
start_x=1
gpu_mem=128
dtoverlay=pi3-disable-bt
enable_uart=1
3. Erstellen der leeren Datei ``ssh``, damit ssh beim nächsten Start aktiviert wird
4. Hinzufügen der ``userconf.txt``
::
compair:$6$eh2pkHm18RgYtwiG$PoeabVCH8llbyIio66OefPGXZ2r2BRI2hPHIdkNTBjmiR0lGXsozGyLx0uViOx3bi998syXjSDXkwt0t3x8Bq.
5. Wlan Verbindung einrichten

16
client/postinstall.sh → postinstall.sh Normal file → Executable file
View file

@ -1,3 +1,10 @@
#!/usr/bin/env bash
if [ "$(uname -m)" = "x86_64" ]; then
echo "Not running on RPi - Skipping postinstall"
exit 0
fi
grep -qxF "apt update" /etc/rc.local
if [ $? -ne 0 ]; then
echo "adding apt update to rc.local"
@ -6,8 +13,10 @@ fi
pip3 install requests flask
echo "Setting up nginx rtmp server"
sudo /etc/init.d/nginx start
#echo "Setting up nginx rtmp server"
#sudo /etc/init.d/nginx start
sudo raspi-config nonint do_legacy 0 || echo "(WARNING) raspi-config not found, cannot enable legacy camera support"
{
echo 'load_module "modules/ngx_rtmp_module.so";'
@ -26,6 +35,9 @@ sudo /etc/init.d/nginx start
echo '}'
} >| /etc/nginx/nginx.conf
echo "Stopping nginx rtmp server as its not required anymore"
sudo /etc/init.d/nginx stop
base64 -d << UPD
CiBfX19fX18gICAgIF9fX19fXyAgICAgX18gICAgX18gICAgIF9fX19fXyAgIF9fICAgICAgICAgX18gICAgIF9fX19fXyAgICAgICAgICAgICAgICAgIAovXCAgX19fXCAgIC9cICBfXyBcICAgL1wgIi0uLyAgXCAgIC9cICA9PSBcIC9cIFwgICAgICAgL1wgXCAgIC9cICA9PSBcICAgICAgICAgICAgICAgICAKXCBcIFxfX19fICBcIFwgXC9cIFwgIFwgXCBcLS4vXCBcICBcIFwgIF8tLyBcIFwgXF9fX18gIFwgXCBcICBcIFwgIF9fPCAgICAgICAgICAgICAgICAgCiBcIFxfX19fX1wgIFwgXF9fX19fXCAgXCBcX1wgXCBcX1wgIFwgXF9cICAgIFwgXF9fX19fXCAgXCBcX1wgIFwgXF9fX19fXCAgICAgICAgICAgICAgIAogIFwvX19fX18vICAgXC9fX19fXy8gICBcL18vICBcL18vICAgXC9fLyAgICAgXC9fX19fXy8gICBcL18vICAgXC9fX19fXy8gICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiBfXyAgX18gICAgIF9fX19fXyAgICAgIF9fX19fXyAgIF9fX19fXyAgICAgICAgX19fX18gICAgIF9fX19fXyAgICAgX19fX19fICAgX19fX19fICAgIAovXCBcL1wgXCAgIC9cICA9PSBcICAgIC9cX18gIF9cIC9cICBfXyBcICAgICAgL1wgIF9fLS4gIC9cICBfXyBcICAgL1xfXyAgX1wgL1wgIF9fX1wgICAKXCBcIFxfXCBcICBcIFwgIF8tLyAgICBcL18vXCBcLyBcIFwgXC9cIFwgICAgIFwgXCBcL1wgXCBcIFwgIF9fIFwgIFwvXy9cIFwvIFwgXCAgX19cICAgCiBcIFxfX19fX1wgIFwgXF9cICAgICAgICAgXCBcX1wgIFwgXF9fX19fXCAgICAgXCBcX19fXy0gIFwgXF9cIFxfXCAgICBcIFxfXCAgXCBcX19fX19cIAogIFwvX19fX18vICAgXC9fLyAgICAgICAgICBcL18vICAgXC9fX19fXy8gICAgICBcL19fX18vICAgXC9fL1wvXy8gICAgIFwvXy8gICBcL19fX19fLyA=
UPD

View file

@ -6,7 +6,7 @@ print("Using version: {str(os.environ['VERSION'])}")
setuptools.setup(
name="complib",
version=str(os.environ["VERSION"]),
version=str(os.environ.get('VERSION', "")),
author="F-WuTs",
author_email="joel.klimont@comp-air.at",
description="",

View file

@ -61,6 +61,60 @@ class SeedingApiTest(unittest.TestCase):
self.assertEqual(seeding_api.get_logistic_plan(), gamestate.get_logistic_plan())
self.assertEqual(seeding_api.get_material_deliveries(), gamestate.get_material_deliveries())
def test_gamestate(self):
seed = 42
gamestate = Seeding.Gamestate(seed)
print(gamestate)
print(gamestate.get_heuballen())
heu_color = gamestate.get_heuballen()
if heu_color == 1:
print("Heuballen liegen auf den gelben Linien")
# TODO: code um die über die gelben Linien zu fahren
elif heu_color == 2:
print("Heuballen liegen auf den blauen Linien")
# TODO: code um die über die blauen Linien zu fahren
materials = gamestate.get_material_deliveries()
print(materials)
for material_pair in materials:
print(f"Der Roboter sollte jetzt die beiden Materialien {material_pair} holen")
for material in material_pair:
if material == 0:
print(f"Der Roboter sollte jetzt Holz aufnehmen, Zone: {material}")
# TODO: code um in die Material Zone mit dem Holz zu fahren
elif material == 1:
print(f"Der Roboter sollte jetzt Stahl aufnehmen, Zone: {material}")
# TODO: code um in die Material Zone mit dem Holz zu fahren
elif material == 2:
print(f"Der Roboter sollte jetzt Beton aufnehmen, Zone: {material}")
# TODO: code um in die Material Zone mit dem Holz zu fahren
elif material == 3:
print(f"Der Roboter sollte jetzt Ziegelsteine aufnehmen, Zone: {material}")
# TODO: code um in die Material Zone mit dem Holz zu fahren
print("Der Roboter sollte jetzt die beiden Materialien zur Baustelle fahren")
# TODO: code um zur Baustelle zu fahren
logistic_plan = gamestate.get_logistic_plan()
print(logistic_plan)
for zone in logistic_plan:
if zone == 10:
print(f"Roboter sollte jetzt zur grünen Zone fahren: {zone}")
# TODO: code um in die grüne Zone zu fahren
elif zone == 11:
print(f"Roboter sollte jetzt zur roten Zone fahren: {zone}")
# TODO: code um in die rote Zone zu fahren
elif zone == 12:
print(f"Roboter sollte jetzt zur blauen Zone fahren: {zone}")
# TODO: code um in die blaue Zone zu fahren
elif zone == 13:
print(f"Roboter sollte jetzt zur gelben Zone fahren: {zone}")
# TODO: code um in die gelbe Zone zu fahren
class DeApiTest(unittest.TestCase):
def test_api_de(self):
@ -92,6 +146,5 @@ class DeApiTest(unittest.TestCase):
self.assertTrue(util_reset_state())
if __name__ == '__main__':
unittest.main()