Compare commits
58 commits
build-test
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
36d4379ddc | ||
|
c593059fb1 | ||
|
ed04957d94 | ||
|
27962d16a7 | ||
|
9c40daae88 | ||
|
de112d98e4 | ||
|
fa4cc1a0ad | ||
|
7e2977a653 | ||
|
cf415d38e8 | ||
|
f09d20ade2 | ||
|
671292a6cf | ||
|
b95b9bf518 | ||
|
dcef53712f | ||
|
c02cfcd71c | ||
|
4c24717278 | ||
|
19d98f1f04 | ||
|
13eefdceb1 | ||
|
f03df1b3b3 | ||
|
ee4f6a516d | ||
|
9e589fd681 | ||
|
be9a5c9f19 | ||
|
765336231b | ||
|
f2724e79f7 | ||
|
a5274c2232 | ||
|
67947116ec | ||
|
369692b619 | ||
|
de9b671f29 | ||
|
b87e830ac3 | ||
|
ff2c529d3a | ||
|
e4592e7963 | ||
|
285996f467 | ||
|
bd0f14a83b | ||
|
1ee4a1e1ef | ||
|
002db9d650 | ||
|
f6e45ac25d | ||
|
af3aaf7998 | ||
|
e1a17808f7 | ||
|
6245d1308a | ||
|
d7a62cfbf5 | ||
|
a84183375b | ||
|
60cfb2669c | ||
|
b6c351dd0b | ||
|
d4a7d8c0c0 | ||
|
c16a7172e7 | ||
|
afe5e9c766 | ||
|
c62b22aa0a | ||
|
807ea8e561 | ||
|
8151173d2a | ||
|
16c8f18c06 | ||
|
03daabaa37 | ||
|
b2281e2a31 | ||
|
8d19e876ab | ||
|
41e88c9fd5 | ||
|
587a7503a2 | ||
|
7a9c4e9f3a | ||
|
077937f5c6 | ||
|
1117f6efa7 | ||
|
1d91792c56 |
217 changed files with 4082 additions and 6827 deletions
60
.github/workflows/complib-package.yml
vendored
Normal file
60
.github/workflows/complib-package.yml
vendored
Normal file
|
@ -0,0 +1,60 @@
|
|||
name: Build comlib.deb package
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build-complib-deb:
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Bump version number
|
||||
uses: paulhatch/semantic-version@v4.0.2
|
||||
id: next_semantic_version
|
||||
with:
|
||||
branch: "master"
|
||||
tag_prefix: ""
|
||||
major_pattern: "(MAJOR)"
|
||||
minor_pattern: "(MINOR)"
|
||||
format: "${major}.${minor}.${patch}-${increment}"
|
||||
bump_each_commit: true
|
||||
- name: Print Version Numbers
|
||||
run: |
|
||||
echo ${{join(steps.next_semantic_version.outputs.*, ' - ')}}
|
||||
echo "Next version: ${{steps.next_semantic_version.outputs.version}}"
|
||||
echo "Next version tag: ${{steps.next_semantic_version.outputs.version_tag}}"
|
||||
- name: Tag Commit
|
||||
uses: actions/github-script@v5
|
||||
with:
|
||||
script: |
|
||||
github.rest.git.createRef({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: 'refs/tags/${{steps.next_semantic_version.outputs.version}}',
|
||||
sha: context.sha
|
||||
})
|
||||
- name: Install fpm
|
||||
run: sudo apt install ruby -y && sudo gem install fpm
|
||||
- name: Build complib deb Package
|
||||
env:
|
||||
VERSION: ${{steps.next_semantic_version.outputs.version}}
|
||||
run: bash ./build.sh # creates packages in "output" directory
|
||||
- name: Pushes to another repository
|
||||
uses: cpina/github-action-push-to-another-repository@main
|
||||
env:
|
||||
API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }}
|
||||
with:
|
||||
source-directory: 'output'
|
||||
target-directory: 'debs/complib/'
|
||||
destination-github-username: 'F-WuTS'
|
||||
destination-repository-name: 'compREP'
|
||||
user-email: joel.klimont@comp-air.at
|
||||
target-branch: master
|
312
.gitignore
vendored
Normal file
312
.gitignore
vendored
Normal file
|
@ -0,0 +1,312 @@
|
|||
# Created by https://www.toptal.com/developers/gitignore/api/macos,python,pycharm
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=macos,python,pycharm
|
||||
|
||||
### macOS ###
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
### macOS Patch ###
|
||||
# iCloud generated files
|
||||
*.icloud
|
||||
|
||||
### PyCharm ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# AWS User-specific
|
||||
.idea/**/aws.xml
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# SonarLint plugin
|
||||
.idea/sonarlint/
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
### PyCharm Patch ###
|
||||
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
|
||||
|
||||
# *.iml
|
||||
# modules.xml
|
||||
# .idea/misc.xml
|
||||
# *.ipr
|
||||
|
||||
# Sonarlint plugin
|
||||
# https://plugins.jetbrains.com/plugin/7973-sonarlint
|
||||
.idea/**/sonarlint/
|
||||
|
||||
# SonarQube Plugin
|
||||
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
|
||||
.idea/**/sonarIssues.xml
|
||||
|
||||
# Markdown Navigator plugin
|
||||
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
|
||||
.idea/**/markdown-navigator.xml
|
||||
.idea/**/markdown-navigator-enh.xml
|
||||
.idea/**/markdown-navigator/
|
||||
|
||||
# Cache file creation bug
|
||||
# See https://youtrack.jetbrains.com/issue/JBR-2257
|
||||
.idea/$CACHE_FILE$
|
||||
|
||||
# CodeStream plugin
|
||||
# https://plugins.jetbrains.com/plugin/12206-codestream
|
||||
.idea/codestream.xml
|
||||
|
||||
# Azure Toolkit for IntelliJ plugin
|
||||
# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
|
||||
.idea/**/azureSettings.xml
|
||||
|
||||
### Python ###
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/macos,python,pycharm
|
17
README.md
17
README.md
|
@ -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)
|
||||
|
|
9
build.sh
Normal file
9
build.sh
Normal file
|
@ -0,0 +1,9 @@
|
|||
#!/usr/bin/bash
|
||||
|
||||
mkdir output
|
||||
|
||||
DEB="empty"
|
||||
|
||||
source build_deb.sh
|
||||
echo "Ran build deb, created: $DEB"
|
||||
mv $DEB ./output
|
|
@ -1,44 +1,54 @@
|
|||
##!/usr/bin/zsh
|
||||
#!/usr/bin/bash
|
||||
|
||||
export PYTHONDONTWRITEBYTECODE=1
|
||||
export VERSION="1.0.0-0"
|
||||
|
||||
# set to 1 for debugging
|
||||
export EXTRACT_PKG="0"
|
||||
|
||||
if [[ -z $VERSION ]]; then
|
||||
echo "Warning, setting VERSION env var to default value 0.0.1-0"
|
||||
export VERSION="0.0.1-0"
|
||||
fi
|
||||
echo "Building Package version: $VERSION"
|
||||
|
||||
# BE CAREFUL TO NOT BUILD IN A PYTHON VENV!
|
||||
|
||||
# be sure to change version if needed!
|
||||
fpm -s python --python-bin python3 --python-pip pip3 --python-package-name-prefix python3 \
|
||||
fpm -s python --python-bin python3 --python-package-name-prefix python3 \
|
||||
-m '"Joel Klimont" <joel.klimont@comp-air.at>' \
|
||||
--license 'proprietary' \
|
||||
--description 'Library for robot used in the competition' \
|
||||
--after-install postinstall.sh \
|
||||
--after-upgrade postinstall.sh \
|
||||
--deb-priority "optional" \
|
||||
--deb-systemd "complib.service" \
|
||||
--architecture "aarch64" \
|
||||
-d "python3-pip" \
|
||||
-d "nginx" \
|
||||
-d "libnginx-mod-rtmp" \
|
||||
-d "libsystemd-dev" \
|
||||
-d "python3-systemd" \
|
||||
-d "gstreamer1.0-tools" \
|
||||
-d "gstreamer1.0-plugins-bad" \
|
||||
-d "gstreamer1.0-plugins-base" \
|
||||
-d "gstreamer1.0-plugins-good" \
|
||||
-d "gstreamer1.0-omx-rpi" \
|
||||
-d "gstreamer1.0-omx-rpi-config" \
|
||||
-d "libatlas-base-dev" \
|
||||
-d "python3-numpy" \
|
||||
-d "opencv-dev" \
|
||||
-d "opencv-libs" \
|
||||
-d "opencv-licenses" \
|
||||
-d "opencv-main" \
|
||||
-d "opencv-python" \
|
||||
-d "opencv-scripts" \
|
||||
-d "libatlas-base-dev" \
|
||||
-d "python3-numpy" \
|
||||
-d "ffmpeg" \
|
||||
-d "libprotobuf23" \
|
||||
-d "protobuf-compiler" \
|
||||
-d "python3-protobuf" \
|
||||
--python-install-lib "/usr/local/lib/python3.9/dist-packages" \
|
||||
-v $VERSION -t deb setup.py
|
||||
|
||||
mkdir build_extract
|
||||
mv python3-complib_"$VERSION"_all.deb build_extract
|
||||
cd build_extract
|
||||
ar -xv python3-complib_"$VERSION"_all.deb
|
||||
if [ "$EXTRACT_PKG" == "1" ]; then
|
||||
echo "Extracting deb package"
|
||||
mkdir build_extract
|
||||
mv python3-complib_"$VERSION"_arm64.deb build_extract
|
||||
cd build_extract
|
||||
ar -xv python3-complib_"$VERSION"_arm64.deb
|
||||
fi
|
||||
|
||||
export DEB=python3-complib_"$VERSION"_arm64.deb
|
||||
echo "Created: $DEB"
|
||||
|
||||
# --deb-changelog changelog \
|
||||
# --deb-upstream-changelog changelog \
|
||||
|
@ -48,4 +58,4 @@ ar -xv python3-complib_"$VERSION"_all.deb
|
|||
# sudo apt purge python3-complib -y
|
||||
# sudo apt install ./python3-*
|
||||
# sudo apt search complib
|
||||
# ar vx ./python3*
|
||||
# ar vx ./python3*
|
|
@ -1,47 +0,0 @@
|
|||
##!/usr/bin/zsh
|
||||
export PYTHONDONTWRITEBYTECODE=1
|
||||
|
||||
# be sure to change version if needed!
|
||||
fpm -s python --python-bin python3 --python-pip pip3 --python-package-name-prefix python3 \
|
||||
-m '"Joel Klimont" <joel.klimont@gmail.com>' \
|
||||
--license 'proprietary' \
|
||||
--description 'Library for robot used in the competition' \
|
||||
--after-install postinstall.sh \
|
||||
--after-upgrade postinstall.sh \
|
||||
--deb-generate-changes \
|
||||
--deb-priority "optional" \
|
||||
--deb-systemd "complib.service" \
|
||||
-d "python3-pip" \
|
||||
-d "nginx" \
|
||||
-d "libnginx-mod-rtmp" \
|
||||
-d "libsystemd-dev" \
|
||||
-d "python3-systemd" \
|
||||
-d "gstreamer1.0-tools" \
|
||||
-d "gstreamer1.0-plugins-bad" \
|
||||
-d "gstreamer1.0-plugins-base" \
|
||||
-d "gstreamer1.0-plugins-good" \
|
||||
-d "gstreamer1.0-omx-rpi" \
|
||||
-d "gstreamer1.0-omx-rpi-config" \
|
||||
-d "opencv-dev" \
|
||||
-d "opencv-libs" \
|
||||
-d "opencv-licenses" \
|
||||
-d "opencv-main" \
|
||||
-d "opencv-python" \
|
||||
-d "opencv-scripts" \
|
||||
-d "libatlas-base-dev" \
|
||||
-d "pigpio" \
|
||||
-d "python-pigpio" \
|
||||
-d "python3-pigpio" \
|
||||
-d "python3-numpy" \
|
||||
-d "ffmpeg" \
|
||||
-v 0.4.1-1 -t deb setup.py
|
||||
|
||||
# --deb-changelog changelog \
|
||||
# --deb-upstream-changelog changelog \
|
||||
# --deb-field "Distribution: stable" \
|
||||
# --deb-dist "stable" \
|
||||
|
||||
#sudo apt purge python3-complib -y
|
||||
#sudo apt install ./python3-*
|
||||
#sudo apt search complib
|
||||
#ar vx ./python3*
|
|
@ -1,5 +0,0 @@
|
|||
python3-complib (0.0.2-4) stable; urgency=low
|
||||
|
||||
* Initial release.
|
||||
|
||||
-- Joel Klimont <joel.klimont@gmail.com> Fri, 15 Jan 2021 23:14:01 +0100
|
|
@ -1,233 +0,0 @@
|
|||
import json
|
||||
import os
|
||||
import time
|
||||
from typing import Dict, Tuple, List
|
||||
|
||||
import requests
|
||||
|
||||
from compLib.LogstashLogging import Logging
|
||||
|
||||
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}")
|
||||
API_URL = api_override
|
||||
|
||||
API_URL_GET_DELIVERY = API_URL + "getDelivery"
|
||||
API_URL_GET_MATERIAL = API_URL + "getMaterial"
|
||||
API_URL_GET_GARBAGE = API_URL + "getGarbage"
|
||||
API_URL_GET_LIST_CARGO = API_URL + "listCargo"
|
||||
API_URL_GET_CARGO = API_URL + "getCargo/"
|
||||
API_URL_GET_ROBOT_STATE = API_URL + "getRobotState"
|
||||
|
||||
API_URL_GET_POS = API_URL + "getPos"
|
||||
API_URL_GET_OP = API_URL + "getOp"
|
||||
API_URL_GET_GOAL = API_URL + "getGoal"
|
||||
API_URL_GET_ITEMS = API_URL + "getItems"
|
||||
API_URL_GET_SCORES = API_URL + "getScores"
|
||||
API_URL_GET_METEOROID = API_URL + "getMeteoroids"
|
||||
|
||||
|
||||
class Seeding:
|
||||
"""Class used for communicating with seeding api
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_delivery() -> Tuple[Dict, int]:
|
||||
"""Makes the /api/getDelivery call to the api.
|
||||
|
||||
:return: Json Object and status code as returned by the api.
|
||||
:rtype: Tuple[Dict, int]
|
||||
"""
|
||||
res = requests.get(API_URL_GET_DELIVERY)
|
||||
result = json.loads(res.content)
|
||||
Logging.get_logger().debug(f"Seeding.get_delivery = {result}, status code = {res.status_code}")
|
||||
return result, res.status_code
|
||||
|
||||
@staticmethod
|
||||
def get_material() -> Tuple[Dict, int]:
|
||||
"""Makes the /api/getMaterial call to the api.
|
||||
|
||||
:return: Json Object and status code as returned by the api.
|
||||
:rtype: Tuple[Dict, int]
|
||||
"""
|
||||
res = requests.get(API_URL_GET_MATERIAL)
|
||||
result = json.loads(res.content)
|
||||
Logging.get_logger().debug(f"Seeding.get_material = {result}, status code = {res.status_code}")
|
||||
return result, res.status_code
|
||||
|
||||
@staticmethod
|
||||
def get_garbage() -> Tuple[Dict, int]:
|
||||
"""Makes the /api/getGarbage call to the api.
|
||||
|
||||
:return: Json Object and status code as returned by the api.
|
||||
:rtype: Tuple[Dict, int]
|
||||
"""
|
||||
res = requests.get(API_URL_GET_GARBAGE)
|
||||
result = json.loads(res.content)
|
||||
Logging.get_logger().debug(f"Seeding.get_garbage {result}, status code = {res.status_code}")
|
||||
return result, res.status_code
|
||||
|
||||
@staticmethod
|
||||
def list_cargo() -> Tuple[Dict, int]:
|
||||
"""Makes the /api/listCargo call to the api.
|
||||
|
||||
:return: Json Object and status code as returned by the api.
|
||||
:rtype: Tuple[Dict, int]
|
||||
"""
|
||||
res = requests.get(API_URL_GET_LIST_CARGO)
|
||||
result = json.loads(res.content)
|
||||
Logging.get_logger().debug(f"Seeding.list_cargo {result}, status code = {res.status_code}")
|
||||
return result, res.status_code
|
||||
|
||||
@staticmethod
|
||||
def get_cargo(color: str) -> Tuple[Dict, int]:
|
||||
"""Makes the /api/getCargo call to the api.
|
||||
|
||||
:param color: Color parameter which specifies which cargo should be taken. (A string which is either "green", "red", "yellow" or "blue") The function only picks up one package.
|
||||
:return: Json Object and status code as returned by the api.
|
||||
:rtype: Tuple[Dict, int]
|
||||
"""
|
||||
res = requests.get(API_URL_GET_CARGO + color)
|
||||
result = json.loads(res.content)
|
||||
Logging.get_logger().debug(f"Seeding.get_cargo {result}, status code = {res.status_code}")
|
||||
return result, res.status_code
|
||||
|
||||
@staticmethod
|
||||
def get_robot_state() -> Tuple[Dict, int]:
|
||||
res = requests.get(API_URL_GET_ROBOT_STATE)
|
||||
result = json.loads(res.content)
|
||||
Logging.get_logger().debug(f"Seeding.get_robot_state {result}, status code = {res.status_code}")
|
||||
return result, res.status_code
|
||||
|
||||
|
||||
class Position:
|
||||
"""Datastructure for holding a position
|
||||
"""
|
||||
|
||||
def __init__(self, x, y, degrees):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.degrees = degrees
|
||||
|
||||
def __repr__(self):
|
||||
return f"Position(x={self.x}, y={self.y}, degrees={self.degrees})"
|
||||
|
||||
def __str__(self):
|
||||
return f"Position(x={round(self.x, 5)}, y={round(self.y, 5)}, degrees={round(self.degrees, 5)})"
|
||||
|
||||
|
||||
class DoubleElim:
|
||||
"""Class used for communicating with double elimination api
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_position() -> Tuple[Position, int]:
|
||||
"""Makes the /api/getPos call to the api.
|
||||
|
||||
:return: A Position object with robot position
|
||||
:rtype: Tuple[Position, int]
|
||||
"""
|
||||
res = requests.get(API_URL_GET_POS)
|
||||
if res.status_code == 408:
|
||||
Logging.get_logger().error(f"DoubleElim.get_position timeout!")
|
||||
time.sleep(0.01)
|
||||
return DoubleElim.get_position()
|
||||
|
||||
response = json.loads(res.content)
|
||||
Logging.get_logger().debug(f"DoubleElim.get_position = {response}, status code = {res.status_code}")
|
||||
return Position(response["x"], response["y"], response["degrees"]), res.status_code
|
||||
|
||||
@staticmethod
|
||||
def get_opponent() -> Tuple[Position, int]:
|
||||
"""Makes the /api/getOp call to the api.
|
||||
|
||||
:return: A Position object with opponents robot position
|
||||
:rtype: Tuple[Position, int]
|
||||
"""
|
||||
res = requests.get(API_URL_GET_OP)
|
||||
if res.status_code == 408:
|
||||
Logging.get_logger().error(f"DoubleElim.get_opponent timeout!")
|
||||
time.sleep(0.01)
|
||||
return DoubleElim.get_opponent()
|
||||
|
||||
response = json.loads(res.content)
|
||||
Logging.get_logger().debug(f"DoubleElim.get_opponent = x:{response}, status code = {res.status_code}")
|
||||
return Position(response["x"], response["y"], response["degrees"]), res.status_code
|
||||
|
||||
@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
|
||||
:rtype: Tuple[Position, int]
|
||||
"""
|
||||
res = requests.get(API_URL_GET_GOAL)
|
||||
if res.status_code == 408:
|
||||
Logging.get_logger().error(f"DoubleElim.get_goal timeout!")
|
||||
time.sleep(0.01)
|
||||
return DoubleElim.get_goal()
|
||||
|
||||
response = json.loads(res.content)
|
||||
Logging.get_logger().debug(f"DoubleElim.get_goal = {response}, status code = {res.status_code}")
|
||||
return Position(response["x"], response["y"], -1), res.status_code
|
||||
|
||||
@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}
|
||||
:rtype: Tuple[List[Dict], int]
|
||||
"""
|
||||
res = requests.get(API_URL_GET_ITEMS)
|
||||
if res.status_code == 408:
|
||||
Logging.get_logger().error(f"DoubleElim.get_items timeout!")
|
||||
time.sleep(0.01)
|
||||
return DoubleElim.get_items()
|
||||
elif res.status_code == 503:
|
||||
return [], 503
|
||||
|
||||
response = json.loads(res.content)
|
||||
Logging.get_logger().debug(f"DoubleElim.get_items = {response}, status code = {res.status_code}")
|
||||
return response, res.status_code
|
||||
|
||||
@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}
|
||||
:rtype: Tuple[Dict, int]
|
||||
"""
|
||||
res = requests.get(API_URL_GET_SCORES)
|
||||
if res.status_code == 408:
|
||||
Logging.get_logger().error(f"DoubleElim.get_scores timeout!")
|
||||
time.sleep(0.01)
|
||||
return DoubleElim.get_scores()
|
||||
elif res.status_code == 503:
|
||||
return {"self": 0, "opponent": 0}, 503
|
||||
|
||||
response = json.loads(res.content)
|
||||
Logging.get_logger().debug(f"DoubleElim.get_scores = {response}, status code = {res.status_code}")
|
||||
return response, res.status_code
|
||||
|
||||
@staticmethod
|
||||
def get_meteoroids() -> Tuple[List[Dict], int]:
|
||||
"""Makes the /api/getMeteoroids call to the api.
|
||||
|
||||
:return: A list will all meteoroids currently on the game field. Meteoroids are dictionaries that look like: {"x": 0, "y": 0}
|
||||
:rtype: Tuple[List[Dict], int]
|
||||
"""
|
||||
res = requests.get(API_URL_GET_METEOROID)
|
||||
if res.status_code == 408:
|
||||
Logging.get_logger().error(f"DoubleElim.get_meteoroids timeout!")
|
||||
time.sleep(0.01)
|
||||
return DoubleElim.get_meteoroids()
|
||||
elif res.status_code == 503:
|
||||
return [], 503
|
||||
|
||||
response = json.loads(res.content)
|
||||
Logging.get_logger().debug(f"DoubleElim.get_meteoroids = {response}, status code = {res.status_code}")
|
||||
return response, res.status_code
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
import string
|
||||
|
||||
from compLib.LogstashLogging import logstash_logger
|
||||
from compLib.Spi import Spi, Register
|
||||
|
||||
LINE_COUNT = 4
|
||||
CHARS_PER_LINE = 16
|
||||
|
||||
|
||||
class Display(object):
|
||||
"""Access the display on the robot.
|
||||
The display is split into 4 Rows and 16 Columns. Each function call changes one line at a time.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def write(line: int, text: str):
|
||||
"""Write a string of text to the integrated display.
|
||||
|
||||
:param line: Line to write. Between 1 and 4
|
||||
:param text: Text to write. Up to 16 characters
|
||||
:raises: IndexError
|
||||
"""
|
||||
if len(text) > CHARS_PER_LINE:
|
||||
logstash_logger.error(f"Too many characters specified!")
|
||||
return
|
||||
|
||||
if line <= 0 or line > LINE_COUNT:
|
||||
raise IndexError("Invalid line number specified")
|
||||
|
||||
to_write = [0] * CHARS_PER_LINE
|
||||
for i in range(len(text)):
|
||||
to_write[i] = ord(text[i])
|
||||
|
||||
if line == 1:
|
||||
Spi.write_array(Register.DISPLAY_LINE_1_C0, CHARS_PER_LINE, to_write)
|
||||
elif line == 2:
|
||||
Spi.write_array(Register.DISPLAY_LINE_2_C0, CHARS_PER_LINE, to_write)
|
||||
elif line == 3:
|
||||
Spi.write_array(Register.DISPLAY_LINE_3_C0, CHARS_PER_LINE, to_write)
|
||||
elif line == 4:
|
||||
Spi.write_array(Register.DISPLAY_LINE_4_C0, CHARS_PER_LINE, to_write)
|
||||
|
||||
@staticmethod
|
||||
def clear():
|
||||
"""Clear the display
|
||||
|
||||
"""
|
||||
for i in range(1, LINE_COUNT + 1):
|
||||
Display.write(i, "")
|
|
@ -1,127 +0,0 @@
|
|||
import atexit
|
||||
from enum import Enum
|
||||
|
||||
from compLib.LogstashLogging import Logging
|
||||
from compLib.Spi import Spi, Register
|
||||
|
||||
MOTOR_COUNT = 4
|
||||
|
||||
|
||||
encoder_start_values = [0] * (MOTOR_COUNT + 1)
|
||||
|
||||
|
||||
class Encoder(object):
|
||||
"""Class used to read the encoders
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def handle_wrap(raw_value, port):
|
||||
"""Handle overflow and underflow of int for encoders.
|
||||
|
||||
:param raw_value: Raw value which was read on port
|
||||
:param port: Port, which the motor is connected to. Between 1 and 4
|
||||
:raises: IndexError
|
||||
:return: Current encoder position
|
||||
"""
|
||||
if port <= 0 or port > MOTOR_COUNT:
|
||||
raise IndexError("Invalid encoder port specified!")
|
||||
|
||||
diff = raw_value - encoder_start_values[port]
|
||||
if diff > 2 ** 31:
|
||||
diff -= 2 ** 32
|
||||
elif diff < -2 ** 31:
|
||||
diff += 2 ** 32
|
||||
|
||||
return diff
|
||||
|
||||
@staticmethod
|
||||
def read_raw(port: int) -> int:
|
||||
"""Read raw encoder from a specified port. Will not be reset to 0 at start.
|
||||
|
||||
:param port: Port, which the motor is connected to. Between 1 and 4
|
||||
:raises: IndexError
|
||||
:return: Current encoder position
|
||||
"""
|
||||
if port <= 0 or port > MOTOR_COUNT:
|
||||
raise IndexError("Invalid encoder port specified!")
|
||||
|
||||
raw_value = 0
|
||||
|
||||
if port == 1:
|
||||
raw_value = Spi.read(Register.MOTOR_1_POS_B3, 4)
|
||||
elif port == 2:
|
||||
raw_value = Spi.read(Register.MOTOR_2_POS_B3, 4)
|
||||
elif port == 3:
|
||||
raw_value = Spi.read(Register.MOTOR_3_POS_B3, 4)
|
||||
elif port == 4:
|
||||
raw_value = Spi.read(Register.MOTOR_4_POS_B3, 4)
|
||||
|
||||
return raw_value
|
||||
|
||||
@staticmethod
|
||||
def read_all_raw():
|
||||
"""Read all encoders at once.
|
||||
This is faster than read_raw as it only uses one SPI call for all encoders!
|
||||
|
||||
:return: Tuple of all current raw encoder positions
|
||||
"""
|
||||
|
||||
encoders = Spi.read_array(Register.MOTOR_1_POS_B3, 4 * 4)
|
||||
|
||||
encoder_1 = int.from_bytes(
|
||||
encoders[0:4], byteorder='big', signed=False)
|
||||
encoder_2 = int.from_bytes(
|
||||
encoders[4:8], byteorder='big', signed=False)
|
||||
encoder_3 = int.from_bytes(
|
||||
encoders[8:12], byteorder='big', signed=False)
|
||||
encoder_4 = int.from_bytes(
|
||||
encoders[12:16], byteorder='big', signed=False)
|
||||
|
||||
return (encoder_1, encoder_2, encoder_3, encoder_4)
|
||||
|
||||
@staticmethod
|
||||
def read_all():
|
||||
"""Read all encoders at once.
|
||||
This is faster than read as it only uses one SPI call for all encoders!
|
||||
|
||||
:return: Tuple of all current encoder positions
|
||||
"""
|
||||
encoders = Encoder.read_all_raw()
|
||||
|
||||
return (Encoder.handle_wrap(encoders[0], 1),
|
||||
Encoder.handle_wrap(encoders[1], 2),
|
||||
Encoder.handle_wrap(encoders[2], 3),
|
||||
Encoder.handle_wrap(encoders[3], 4))
|
||||
|
||||
@staticmethod
|
||||
def read(port: int) -> int:
|
||||
"""Read encoder from a specified port
|
||||
|
||||
:param port: Port, which the motor is connected to. Between 1 and 4
|
||||
:raises: IndexError
|
||||
:return: Current encoder position
|
||||
"""
|
||||
if port <= 0 or port > MOTOR_COUNT:
|
||||
raise IndexError("Invalid encoder port specified!")
|
||||
|
||||
return Encoder.handle_wrap(Encoder.read_raw(port), port)
|
||||
|
||||
@staticmethod
|
||||
def clear(port: int):
|
||||
"""Reset encoder position to 0
|
||||
|
||||
:param port: Port, which the motor is connected to. Between 1 and 4
|
||||
:raises: IndexError
|
||||
"""
|
||||
if port <= 0 or port > MOTOR_COUNT:
|
||||
raise IndexError("Invalid encoder port specified!")
|
||||
|
||||
encoder_start_values[port] = Encoder.read_raw(port)
|
||||
|
||||
@staticmethod
|
||||
def clear_all():
|
||||
"""Reset all encoder positions to 0
|
||||
"""
|
||||
|
||||
for i in range(1, MOTOR_COUNT + 1):
|
||||
encoder_start_values[i] = Encoder.read_raw(i)
|
|
@ -1,116 +0,0 @@
|
|||
import datetime
|
||||
import os
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
|
||||
import systemd.daemon
|
||||
from compLib.Lock import Lock
|
||||
|
||||
try:
|
||||
from compLib.LogstashLogging import Logging
|
||||
except Exception as e:
|
||||
import logging
|
||||
|
||||
|
||||
class Logger():
|
||||
def __init__(self):
|
||||
self.logger = logging.Logger('compApi background')
|
||||
|
||||
def get_logger(self):
|
||||
return self.logger
|
||||
|
||||
Logging = Logger()
|
||||
print(f"Could not import compLib.LogstashLogging: {str(e)}")
|
||||
Logging.get_logger().error(f"Could not import compLib.LogstashLogging: {str(e)}")
|
||||
|
||||
print("after basic imports")
|
||||
|
||||
RUN_IP_CHECK = False
|
||||
try:
|
||||
from compLib.Display import Display
|
||||
from compLib.Spi import Spi
|
||||
from compLib import __version__
|
||||
RUN_IP_CHECK = True
|
||||
except Exception as e:
|
||||
print(f"Could not import display or spi for ip output {str(e)}")
|
||||
Logging.get_logger().warning(f"Could not import display or spi for ip output {str(e)}")
|
||||
|
||||
print(f"After display and Spi import")
|
||||
|
||||
__run = """raspivid -t 0 -b 5000000 -w 1280 -h 720 -fps 30 -n -o - | gst-launch-1.0 fdsrc ! video/x-h264,width=1280,height=720,framerate=30/1,noise-reduction=1,profile=high,stream-format=byte-stream ! h264parse ! queue ! flvmux streamable=true ! rtmpsink location=\"rtmp://localhost/live/stream\""""
|
||||
|
||||
STREAM_RASPI = False if os.getenv("STREAM_RASPI", "false") == "false" else True
|
||||
IP_OUTPUT = False if os.getenv("IP_OUTPUT", "true") != "true" else True
|
||||
|
||||
|
||||
def get_ip():
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
try:
|
||||
s.connect(('10.255.255.255', 1))
|
||||
IP = s.getsockname()[0]
|
||||
except Exception:
|
||||
IP = 'Not connected'
|
||||
print(f"Error could not query ip: {e}")
|
||||
finally:
|
||||
s.close()
|
||||
return IP
|
||||
|
||||
|
||||
def write_ip_to_screen():
|
||||
while os.getenv("IP_OUTPUT", "true") == "true":
|
||||
try:
|
||||
if not Lock.is_locked():
|
||||
Lock.lock()
|
||||
ip = str(get_ip())
|
||||
print(f"writing {ip} to display")
|
||||
Display.write(2, f"LIB: V{__version__}")
|
||||
Display.write(3, f"FW: V{Spi.get_version()}")
|
||||
Display.write(4, f"IP: {ip}")
|
||||
Display.write(1, datetime.datetime.now().strftime("%b %d %H:%M:%S"))
|
||||
Lock.unlock()
|
||||
|
||||
time.sleep(10)
|
||||
except Exception as e:
|
||||
print(f"Exception in write ip thread: {e}")
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
systemd.daemon.notify(systemd.daemon.Notification.READY)
|
||||
except:
|
||||
Logging.get_logger().warning("Warning, old systemd version detected")
|
||||
systemd.daemon.notify('READY=1')
|
||||
|
||||
|
||||
ip_output = None
|
||||
if RUN_IP_CHECK and IP_OUTPUT:
|
||||
print("starting ip output")
|
||||
Logging.get_logger().info("starting ip output")
|
||||
Lock.unlock()
|
||||
try:
|
||||
Spi.disable_health_check()
|
||||
ip_output = threading.Thread(target=write_ip_to_screen)
|
||||
ip_output.start()
|
||||
print("starting ip output - DONE")
|
||||
Logging.get_logger().info("starting ip output - DONE")
|
||||
except Exception as e:
|
||||
print(f"could not start ip output -> {str(e)}")
|
||||
Logging.get_logger().error(f"could not start ip output -> {str(e)}")
|
||||
|
||||
if STREAM_RASPI:
|
||||
print("starting gstreamer background process")
|
||||
Logging.get_logger().info("starting gstreamer background process")
|
||||
os.system(__run)
|
||||
print("gstreamer stopped...")
|
||||
Logging.get_logger().error("gstreamer stopped...")
|
||||
else:
|
||||
print("not starting gstreamer background process")
|
||||
Logging.get_logger().info("not starting gstreamer background process")
|
||||
if ip_output is not None:
|
||||
ip_output.join()
|
||||
else:
|
||||
print("ip display output failed to initialize.. sleeping for a day, good night")
|
||||
Logging.get_logger().info("ip display output failed to initialize.. sleeping for a day, good night")
|
||||
time.sleep(60 * 60 * 24)
|
|
@ -1,81 +0,0 @@
|
|||
from compLib.LogstashLogging import Logging
|
||||
from compLib.MetricsLogging import MetricsLogging
|
||||
from compLib.Spi import Spi, Register
|
||||
|
||||
SENSOR_COUNT = 5
|
||||
|
||||
|
||||
class IRSensor(object):
|
||||
"""Access the different IR Sensors of the robot
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def read(sensor: int) -> int:
|
||||
"""Read one infrared sensor
|
||||
|
||||
:param sensor: Which sensor to read. Between 1 and 5
|
||||
:raises: IndexError
|
||||
:return: Sensor value. 10 bit accuracy
|
||||
:rtype: int
|
||||
"""
|
||||
if sensor <= 0 or sensor > SENSOR_COUNT:
|
||||
raise IndexError("Invalid sensor specified!")
|
||||
|
||||
result = 0
|
||||
if sensor == 1:
|
||||
result = Spi.read(Register.IR_1_H, 2)
|
||||
elif sensor == 2:
|
||||
result = Spi.read(Register.IR_2_H, 2)
|
||||
elif sensor == 3:
|
||||
result = Spi.read(Register.IR_3_H, 2)
|
||||
elif sensor == 4:
|
||||
result = Spi.read(Register.IR_4_H, 2)
|
||||
elif sensor == 5:
|
||||
result = Spi.read(Register.IR_5_H, 2)
|
||||
|
||||
MetricsLogging.put("Infrared", result, sensor)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def read_all():
|
||||
"""Read all IR sensors at once.
|
||||
This is faster than read as it only uses one SPI call for all sensors!
|
||||
|
||||
:return: Tuple of all current ir sensors
|
||||
"""
|
||||
|
||||
sensors = Spi.read_array(Register.IR_1_H, 5 * 2)
|
||||
|
||||
sensor_1 = int.from_bytes(
|
||||
sensors[0:2], byteorder='big', signed=False)
|
||||
sensor_2 = int.from_bytes(
|
||||
sensors[2:4], byteorder='big', signed=False)
|
||||
sensor_3 = int.from_bytes(
|
||||
sensors[4:6], byteorder='big', signed=False)
|
||||
sensor_4 = int.from_bytes(
|
||||
sensors[6:8], byteorder='big', signed=False)
|
||||
sensor_5 = int.from_bytes(
|
||||
sensors[8:10], byteorder='big', signed=False)
|
||||
|
||||
return (sensor_1, sensor_2, sensor_3, sensor_4, sensor_5)
|
||||
|
||||
@staticmethod
|
||||
def set(sensor: int, on: bool):
|
||||
"""Turn on / off a IR emitter
|
||||
|
||||
:param sensor: Which sensor to read. Between 1 and 5
|
||||
:raises: IndexError
|
||||
"""
|
||||
if sensor <= 0 or sensor > SENSOR_COUNT:
|
||||
raise IndexError("Invalid sensor specified!")
|
||||
|
||||
if sensor == 1:
|
||||
Spi.write(Register.IR_1_LED, 1, on)
|
||||
elif sensor == 2:
|
||||
Spi.write(Register.IR_2_LED, 1, on)
|
||||
elif sensor == 3:
|
||||
Spi.write(Register.IR_3_LED, 1, on)
|
||||
elif sensor == 4:
|
||||
Spi.write(Register.IR_4_LED, 1, on)
|
||||
elif sensor == 5:
|
||||
Spi.write(Register.IR_5_LED, 1, on)
|
|
@ -1,25 +0,0 @@
|
|||
from filelock import FileLock, Timeout
|
||||
import atexit
|
||||
|
||||
FILELOCK_PATH = "/root/complib.lock"
|
||||
|
||||
global_lock = FileLock(FILELOCK_PATH, timeout=1)
|
||||
class Lock(object):
|
||||
@staticmethod
|
||||
def lock():
|
||||
global_lock.acquire()
|
||||
|
||||
@staticmethod
|
||||
def unlock():
|
||||
global_lock.release()
|
||||
|
||||
@staticmethod
|
||||
def is_locked():
|
||||
try:
|
||||
global_lock.acquire()
|
||||
global_lock.release()
|
||||
return False
|
||||
except Timeout:
|
||||
return True
|
||||
|
||||
atexit.register(Lock.unlock)
|
|
@ -1,102 +0,0 @@
|
|||
import logging
|
||||
import os
|
||||
import sys
|
||||
import requests
|
||||
|
||||
from logstash_async.transport import HttpTransport
|
||||
from logstash_async.handler import AsynchronousLogstashHandler
|
||||
|
||||
EXTENSIVE_LOGGING = os.getenv("EXTENSIVE_LOGGING", "False")
|
||||
|
||||
if EXTENSIVE_LOGGING == "True":
|
||||
EXTENSIVE_LOGGING = True
|
||||
else:
|
||||
EXTENSIVE_LOGGING = False
|
||||
|
||||
host = 'logstash.robo4you.at'
|
||||
port = 443
|
||||
|
||||
logstash_logger = logging.getLogger('logstash')
|
||||
logstash_logger.setLevel(logging.INFO)
|
||||
|
||||
transport = HttpTransport(
|
||||
host,
|
||||
port,
|
||||
username="robo",
|
||||
password="competition",
|
||||
timeout=60.0,
|
||||
)
|
||||
|
||||
asynchronousLogstashHandler = AsynchronousLogstashHandler(
|
||||
host,
|
||||
port,
|
||||
transport=transport,
|
||||
database_path='logs.db'
|
||||
)
|
||||
|
||||
|
||||
class StreamToLogger(object):
|
||||
"""
|
||||
Fake file-like stream object that redirects writes to a logger instance.
|
||||
"""
|
||||
|
||||
def __init__(self, textio, log_level=logging.INFO):
|
||||
self.logger = logging.getLogger('logstash')
|
||||
self.console = textio
|
||||
self.log_level = log_level
|
||||
self.linebuf = ''
|
||||
|
||||
def write(self, buf):
|
||||
self.console.write(buf)
|
||||
temp_linebuf = self.linebuf + buf
|
||||
for line in buf.splitlines(True):
|
||||
self.linebuf += line
|
||||
|
||||
def flush(self):
|
||||
if self.linebuf != '':
|
||||
self.logger.log(self.log_level, self.linebuf.rstrip())
|
||||
self.linebuf = ''
|
||||
self.console.flush()
|
||||
asynchronousLogstashHandler.flush()
|
||||
|
||||
|
||||
if EXTENSIVE_LOGGING:
|
||||
try:
|
||||
r = requests.get(f"https://{host}:{port}")
|
||||
if r.status_code == 401:
|
||||
logstash_logger.addHandler(asynchronousLogstashHandler)
|
||||
|
||||
so = StreamToLogger(sys.stdout, logging.INFO)
|
||||
sys.stdout = so
|
||||
|
||||
se = StreamToLogger(sys.stderr, logging.ERROR)
|
||||
sys.stderr = se
|
||||
else:
|
||||
print(f"Could not connect to {host} -> ERROR CODE: {r.status_code}!")
|
||||
|
||||
except requests.exceptions.ConnectionError as identifier:
|
||||
print(f"Could not connect to {host}!")
|
||||
print(f"Error loading logger was -> {identifier}")
|
||||
else:
|
||||
print("Extensive logging is disabled! No logs will be sent over network...")
|
||||
|
||||
|
||||
class Logging(object):
|
||||
"""Can be used to manually use the logger or turn up the logging level used for debugging this library.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_logger() -> logging.Logger:
|
||||
"""Get the logger object used to communicate with logstash
|
||||
|
||||
:return: Python logger
|
||||
:rtype: logging.Logger
|
||||
"""
|
||||
return logstash_logger
|
||||
|
||||
@staticmethod
|
||||
def set_debug():
|
||||
"""Turns up the logging level of the library to debug. Should be used, when debugging with the
|
||||
Competition organizers
|
||||
"""
|
||||
logstash_logger.setLevel(logging.DEBUG)
|
|
@ -1,97 +0,0 @@
|
|||
from influxdb_client import InfluxDBClient, Point, WritePrecision
|
||||
import socket
|
||||
import uuid
|
||||
import os
|
||||
import multiprocessing
|
||||
import datetime
|
||||
import requests
|
||||
import time
|
||||
|
||||
CONCURRENCY = 1
|
||||
|
||||
HOSTNAME = socket.gethostname()
|
||||
RUN_TRACE = str(uuid.uuid4())
|
||||
|
||||
TOKEN = "aCdxnntw-Zp3agci8Z32lQAR_ep6MhdmWOJG7ObnoikYqe7nKAhFYx1jVGBpipQuId79SC4Jl0J6IBYVqauJyw=="
|
||||
ORG = "robo4you"
|
||||
BUCKET = "compAIR"
|
||||
|
||||
INFLUX_HOST = "https://influxdb.comp-air.at"
|
||||
|
||||
EXTENSIVE_LOGGING = os.getenv("EXTENSIVE_LOGGING", "True")
|
||||
if EXTENSIVE_LOGGING == "True":
|
||||
EXTENSIVE_LOGGING = True
|
||||
else:
|
||||
EXTENSIVE_LOGGING = False
|
||||
|
||||
|
||||
influx_client = InfluxDBClient(url=INFLUX_HOST, token=TOKEN)
|
||||
write_api = influx_client.write_api()
|
||||
|
||||
point_queue = multiprocessing.Queue()
|
||||
workers = []
|
||||
|
||||
class MetricsLogging():
|
||||
"""Used to send metrics / points to a influxdb
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def is_influx_reachable() -> bool:
|
||||
"""Check if we can send metrics to the database
|
||||
|
||||
:return: Is reachable?
|
||||
:rtype: bool
|
||||
"""
|
||||
try:
|
||||
r = requests.get(INFLUX_HOST)
|
||||
if r.status_code == 200:
|
||||
return True
|
||||
else:
|
||||
print(f"Could not connect to {INFLUX_HOST} -> ERROR CODE: {r.status_code}!")
|
||||
return False
|
||||
except requests.exceptions.RequestException as identifier:
|
||||
print(f"Could not connect to {INFLUX_HOST}!")
|
||||
|
||||
@staticmethod
|
||||
def put(sensorType, value, port):
|
||||
"""Put a datapoint into a time-series-database
|
||||
|
||||
:param sensorType: A key used to identify a datapoint
|
||||
:param value: Value measured by the sensor
|
||||
:param port: Port of the sensor which was read
|
||||
"""
|
||||
if EXTENSIVE_LOGGING:
|
||||
point = Point(sensorType) \
|
||||
.tag("host", HOSTNAME) \
|
||||
.tag("runID", RUN_TRACE) \
|
||||
.tag("port", port) \
|
||||
.field("value", value) \
|
||||
.time(datetime.datetime.utcnow(), WritePrecision.MS)
|
||||
point_queue.put_nowait(point)
|
||||
|
||||
@staticmethod
|
||||
def worker():
|
||||
point = object()
|
||||
for job in iter(point_queue.get, point):
|
||||
try:
|
||||
write_api.write(BUCKET, ORG, point)
|
||||
time.sleep(0.1)
|
||||
except Exception as e:
|
||||
pass
|
||||
finally:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def start_workers():
|
||||
global EXTENSIVE_LOGGING
|
||||
if EXTENSIVE_LOGGING:
|
||||
if MetricsLogging.is_influx_reachable():
|
||||
for i in range(CONCURRENCY):
|
||||
worker = multiprocessing.Process(target=MetricsLogging.worker, daemon=True)
|
||||
worker.start()
|
||||
workers.append(worker)
|
||||
else:
|
||||
EXTENSIVE_LOGGING = False
|
||||
|
||||
|
||||
MetricsLogging.start_workers()
|
|
@ -1,226 +0,0 @@
|
|||
import atexit
|
||||
from enum import IntEnum
|
||||
|
||||
from compLib.LogstashLogging import Logging
|
||||
from compLib.MetricsLogging import MetricsLogging
|
||||
from compLib.Spi import Spi, Register
|
||||
|
||||
MOTOR_COUNT = 4
|
||||
MAX_MOTOR_SPEED = 65535
|
||||
MOTOR_PERCENTAGE_MULT = MAX_MOTOR_SPEED / 100.0
|
||||
NEW_MOTOR_CURVE = [0.0, 0.0, 0.5, 60.0, 199.83333333333334, 377.66666666666663, 990.3333333333333, 1860.6666666666665, 2587.0, 3091.6666666666665, 3489.0, 3860.5, 4197.333333333333, 4432.166666666667, 4647.166666666666, 4873.166666666666, 5054.333333333334, 5208.666666666667, 5353.0, 5466.5, 5604.0]
|
||||
MOTOR_CURVE = [0.0, 0.0, 426.5, 692.0, 842.5, 953.5, 1032.5, 1090.5, 1135.5, 1171.0, 1203.5, 1230.0, 1249.5, 1268.0, 1283.0, 1298.5, 1308.0, 1320.0, 1332.0, 1339.5, 1352.5]
|
||||
|
||||
SPEED_LOCK = False
|
||||
SPEED_MULT = 1.0
|
||||
|
||||
|
||||
class MotorMode(IntEnum):
|
||||
COAST = 0,
|
||||
FORWARD = 1,
|
||||
BACKWARD = 2,
|
||||
BREAK = 3
|
||||
|
||||
|
||||
class Motor(object):
|
||||
"""Class used to control the motors
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def pwm(port: int, pwm: int, mode: MotorMode):
|
||||
"""Set specified motor to a specific pwm value and motor mode
|
||||
|
||||
:param port: Port, which the motor is connected to. 1-4 allowed
|
||||
:param pwm: Raw PWM value which the motor should be set to. From 0 to 2^16 - 1
|
||||
:param mode: Motor mode. See enum MotorMode for more info
|
||||
:raises: IndexError
|
||||
"""
|
||||
|
||||
if SPEED_LOCK:
|
||||
return
|
||||
|
||||
if port <= 0 or port > MOTOR_COUNT:
|
||||
raise IndexError("Invalid Motor port specified!")
|
||||
|
||||
if port == 1:
|
||||
Spi.write(Register.MOTOR_1_PWM_H, 2, pwm)
|
||||
Spi.write(Register.PWM_1_CTRL, 1, int(mode))
|
||||
elif port == 2:
|
||||
Spi.write(Register.MOTOR_2_PWM_H, 2, pwm)
|
||||
Spi.write(Register.PWM_2_CTRL, 1, int(mode))
|
||||
elif port == 3:
|
||||
Spi.write(Register.MOTOR_3_PWM_H, 2, pwm)
|
||||
Spi.write(Register.PWM_3_CTRL, 1, int(mode))
|
||||
elif port == 4:
|
||||
Spi.write(Register.MOTOR_4_PWM_H, 2, pwm)
|
||||
Spi.write(Register.PWM_4_CTRL, 1, int(mode))
|
||||
|
||||
@staticmethod
|
||||
def power_raw(port: int, percent: float, log_metric = True):
|
||||
"""Set specified motor to percentage power
|
||||
|
||||
:param port: Port, which the motor is connected to. 1-4
|
||||
:param percent: Percentage of max speed. between -100 and 100
|
||||
:raises: IndexError
|
||||
"""
|
||||
|
||||
if port <= 0 or port > MOTOR_COUNT:
|
||||
raise IndexError("Invalid Motor port specified!")
|
||||
|
||||
if percent < -100 or percent > 100:
|
||||
raise IndexError("Invalid Motor speed specified! Speed is between -100 and 100 percent!")
|
||||
|
||||
if log_metric:
|
||||
MetricsLogging.put("MotorRaw", float(percent), port)
|
||||
|
||||
mode = MotorMode.COAST
|
||||
if percent < 0:
|
||||
percent = abs(percent)
|
||||
mode = MotorMode.BACKWARD
|
||||
elif percent > 0:
|
||||
mode = MotorMode.FORWARD
|
||||
|
||||
percent *= SPEED_MULT
|
||||
pwm = percent * MOTOR_PERCENTAGE_MULT
|
||||
|
||||
Motor.pwm(port, int(pwm), mode)
|
||||
|
||||
@staticmethod
|
||||
def power(port: int, percent: float):
|
||||
"""Set specified motor to percentage power, percentage is linearized
|
||||
so that it's roughly proportional to the speed
|
||||
|
||||
:param port: Port, which the motor is connected to. 1-4
|
||||
:param percent: Percentage of max speed. between -100 and 100
|
||||
:raises: IndexError
|
||||
"""
|
||||
raw_power = raw_power = Motor.__linearizePower(MOTOR_CURVE, abs(percent))
|
||||
if percent < 0:
|
||||
raw_power *= -1
|
||||
|
||||
MetricsLogging.put("Motor", float(percent), port)
|
||||
Motor.power_raw(port, raw_power, False)
|
||||
|
||||
@staticmethod
|
||||
def all_off():
|
||||
"""
|
||||
Turns of all motors
|
||||
"""
|
||||
Logging.get_logger().debug(f"Motor.all_off")
|
||||
|
||||
for i in range(1, MOTOR_COUNT + 1):
|
||||
Motor.active_break(i)
|
||||
|
||||
@staticmethod
|
||||
def active_break(port: int):
|
||||
"""
|
||||
Actively break with a specific motor
|
||||
|
||||
:param port: Port, which the motor is connected to. 1-4
|
||||
"""
|
||||
Motor.pwm(port, 0, MotorMode.BREAK)
|
||||
|
||||
@staticmethod
|
||||
def set_motor_curve(curve):
|
||||
"""
|
||||
Set the global motor curve, must be a float array with exactly 21 elements.
|
||||
[0] = x ticks/s (0% power)
|
||||
[1] = x ticks/s (5% power)
|
||||
[2] = x ticks/s (10% power)
|
||||
...
|
||||
[20] = x ticks/s (100% power)
|
||||
|
||||
:param curve: float array with 21 elements
|
||||
:raises: ValueError
|
||||
"""
|
||||
if (len(curve) != 21):
|
||||
raise ValueError('The motor curve is invalid, check documentation for set_motor_curve()!')
|
||||
|
||||
global MOTOR_CURVE
|
||||
MOTOR_CURVE = curve
|
||||
|
||||
@staticmethod
|
||||
def get_motor_curve():
|
||||
"""
|
||||
Get the currently active motor curve. Check set_motor_curve() for more info.
|
||||
|
||||
:return: current motor curve
|
||||
"""
|
||||
return MOTOR_CURVE
|
||||
|
||||
@staticmethod
|
||||
def __map(x, in_min, in_max, out_min, out_max):
|
||||
"""
|
||||
Linear interpolation. Check https://www.arduino.cc/reference/en/language/functions/math/map/
|
||||
"""
|
||||
x = float(x)
|
||||
in_min = float(in_min)
|
||||
in_max = float(in_max)
|
||||
out_min = float(out_min)
|
||||
out_max = float(out_max)
|
||||
|
||||
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
|
||||
|
||||
@staticmethod
|
||||
def __interpolateSpeed(curve, speed):
|
||||
"""
|
||||
Interpolate a speed in the specified motor curve and return
|
||||
the 'power percentage that is needed to reach that speed'
|
||||
"""
|
||||
if speed < min(curve) or speed > max(curve):
|
||||
raise ValueError(f'Speed out of range: {str(speed)} ticks/s')
|
||||
|
||||
for index in range(len(curve) - 1):
|
||||
if speed >= curve[index] and speed <= curve[index + 1] and curve[index] != curve[index + 1]:
|
||||
return Motor.__map(speed, curve[index], curve[index + 1], index * 5, index * 5 + 5)
|
||||
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
def __linearizePower(curve, power):
|
||||
"""
|
||||
Takes raw power and returns it corrected so that the motor speed would be roughly linear
|
||||
"""
|
||||
if power < 0 or power > 100:
|
||||
raise ValueError(f'Power out of range: {str(power)}%')
|
||||
|
||||
requiredSpeed = Motor.__map(power, 0, 100, min(curve), max(curve))
|
||||
|
||||
return Motor.__interpolateSpeed(curve, requiredSpeed)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def robot_state_loop():
|
||||
import time
|
||||
from compLib.Api import Seeding
|
||||
|
||||
global SPEED_LOCK
|
||||
global SPEED_MULT
|
||||
|
||||
while True:
|
||||
try:
|
||||
state, status = Seeding.get_robot_state()
|
||||
SPEED_MULT = state["speed"] / 100.0
|
||||
|
||||
if state["left"] != -1 or state["right"] != -1:
|
||||
Motor.power(1, state["right"])
|
||||
Motor.power(4, state["left"])
|
||||
SPEED_LOCK = True
|
||||
time.sleep(state["time"])
|
||||
SPEED_LOCK = False
|
||||
Motor.power(1, 0)
|
||||
Motor.power(4, 0)
|
||||
else:
|
||||
time.sleep(0.25)
|
||||
except:
|
||||
time.sleep(0.25)
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def start_robot_state_loop():
|
||||
from threading import Thread
|
||||
robot_state_thread = Thread(target=Motor.robot_state_loop)
|
||||
robot_state_thread.setDaemon(True)
|
||||
robot_state_thread.start()
|
||||
|
||||
atexit.register(Motor.all_off)
|
|
@ -1,101 +0,0 @@
|
|||
import time
|
||||
import math
|
||||
from compLib.Encoder import Encoder
|
||||
from compLib.Robot import Robot
|
||||
|
||||
last_run = time.time()
|
||||
last_enc_left = 0
|
||||
last_enc_right = 0
|
||||
|
||||
pos_x = 0
|
||||
pos_y = 0
|
||||
orientation = 0
|
||||
|
||||
|
||||
class Odometry():
|
||||
"""DTO used for holding all odometry information. \n
|
||||
Coordinate system: \n
|
||||
X: + Forward; - Backwards \n
|
||||
Y: + Right; - Left \n
|
||||
Orientation: + Right; - Left
|
||||
"""
|
||||
|
||||
def __init__(self, x, y, orientation):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.orientation = orientation
|
||||
|
||||
def get_x(self) -> float:
|
||||
"""Returns distance driven on x-axis in meters"""
|
||||
return self.x
|
||||
|
||||
def get_y(self) -> float:
|
||||
"""Returns distance driven on y-axis in meters"""
|
||||
return self.y
|
||||
|
||||
def get_orientation(self) -> float:
|
||||
"""Returns degrees turned in radians"""
|
||||
return self.orientation
|
||||
|
||||
def __str__(self):
|
||||
return f"X: {self.x} Y: {self.y} O: {self.orientation}"
|
||||
|
||||
|
||||
class Odom(object):
|
||||
"""Class used to track the movement of the robot in X, Y, Theta (Orientation)
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_odom() -> Odometry:
|
||||
"""
|
||||
:return: Current orientation of the robot
|
||||
"""
|
||||
return Odometry(pos_x, pos_y, orientation)
|
||||
|
||||
@staticmethod
|
||||
def clear() -> None:
|
||||
"""
|
||||
Clears the current odometry information and start from X, Y, Orientation set to 0
|
||||
"""
|
||||
global last_run, last_enc_left, last_enc_right, pos_x, pos_y, orientation
|
||||
|
||||
last_run = 0
|
||||
last_enc_left = 0
|
||||
last_enc_right = 0
|
||||
pos_x = 0
|
||||
pos_y = 0
|
||||
orientation = 0
|
||||
|
||||
@staticmethod
|
||||
def update() -> None:
|
||||
"""
|
||||
Updates the current odometry information of the robot, Should be called in a loop with at least 100HZ.
|
||||
Do not clear encoder positions between updates! Must be cleared, when clearing encoders!
|
||||
"""
|
||||
global last_run, last_enc_left, last_enc_right, pos_x, pos_y, orientation
|
||||
|
||||
now = time.time()
|
||||
if last_run + 0.01 < now:
|
||||
last_run = now
|
||||
|
||||
encoder_positions = Encoder.read_all()
|
||||
current_left = encoder_positions[Robot.LEFT_PORT - 1]
|
||||
current_right = -encoder_positions[Robot.RIGHT_PORT - 1]
|
||||
distance_left = (current_left - last_enc_left) / Robot.TICKS_PER_METER
|
||||
distance_right = (current_right - last_enc_right) / Robot.TICKS_PER_METER
|
||||
last_enc_left = current_left
|
||||
last_enc_right = current_right
|
||||
|
||||
distance = (distance_left + distance_right) / 2.0
|
||||
theta = (distance_left - distance_right) / Robot.ARBOR_LENGTH_M
|
||||
|
||||
if distance != 0:
|
||||
distance_x = math.cos(theta) * distance
|
||||
distance_y = -math.sin(theta) * distance
|
||||
|
||||
pos_x = pos_x + (math.cos(orientation) * distance_x -
|
||||
math.sin(orientation) * distance_y)
|
||||
pos_y = pos_y + (math.sin(orientation) * distance_x +
|
||||
math.cos(orientation) * distance_y)
|
||||
if theta != 0:
|
||||
orientation += theta
|
|
@ -1,24 +0,0 @@
|
|||
import RPi.GPIO as GPIO
|
||||
import time
|
||||
|
||||
GPIO.setwarnings(False)
|
||||
RESET_PIN = 23
|
||||
BOOT_PIN = 17
|
||||
|
||||
class Reset:
|
||||
"""Reset the co-processor
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def reset_bot():
|
||||
"""Reset the co-processor
|
||||
"""
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
GPIO.setup(RESET_PIN, GPIO.OUT)
|
||||
GPIO.setup(BOOT_PIN, GPIO.OUT)
|
||||
|
||||
GPIO.output(RESET_PIN, GPIO.LOW)
|
||||
GPIO.output(BOOT_PIN, GPIO.LOW)
|
||||
time.sleep(0.1)
|
||||
GPIO.output(RESET_PIN, GPIO.HIGH)
|
||||
time.sleep(1.5)
|
|
@ -1,24 +0,0 @@
|
|||
import math
|
||||
|
||||
|
||||
class Robot(object):
|
||||
WHEEL_CIRCUMFERENCE_MM = 71.0 * math.pi
|
||||
"""Circumference of a wheel in millimeters"""
|
||||
|
||||
TICKS_PER_TURN = 27.7 * 100.0
|
||||
"""Ticks per 360 degree turn of a wheel"""
|
||||
|
||||
ARBOR_LENGTH_MM = 139.0
|
||||
"""Distance between the two wheels in millimeters"""
|
||||
|
||||
ARBOR_LENGTH_M = ARBOR_LENGTH_MM / 1000.0
|
||||
"""Distance between the two wheels in meters"""
|
||||
|
||||
TICKS_PER_METER = 1000.0 / WHEEL_CIRCUMFERENCE_MM * TICKS_PER_TURN
|
||||
"""Ticks after driving one meter"""
|
||||
|
||||
LEFT_PORT = 4
|
||||
"""Motor port for the left motor"""
|
||||
|
||||
RIGHT_PORT = 1
|
||||
"""Motor port for the right motor"""
|
|
@ -1,63 +0,0 @@
|
|||
from compLib.Spi import Spi, Register
|
||||
|
||||
|
||||
SERVO_COUNT = 8
|
||||
|
||||
MIN_ANGLE = -90.0
|
||||
MAX_ANGLE = 90.0
|
||||
|
||||
|
||||
def map(x, in_min, in_max, out_min, out_max):
|
||||
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
|
||||
|
||||
|
||||
class Servo:
|
||||
"""Control the servo ports on the robot
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def set_position(port: int, angle: float, offset: float = 90.0):
|
||||
"""Set position of servo connected to port
|
||||
|
||||
:param port: port between 1 and 8
|
||||
:param angle: Angle of servo
|
||||
"""
|
||||
if port <= 0 or port > SERVO_COUNT:
|
||||
raise IndexError("Invalid Servo port specified!")
|
||||
|
||||
angle = max(min(angle, MAX_ANGLE), MIN_ANGLE)
|
||||
|
||||
mapped_angle = int(map(angle, MIN_ANGLE, MAX_ANGLE, 0, 65535))
|
||||
|
||||
if port == 1:
|
||||
Spi.write(Register.SERVO_1_PWM_H, 2, mapped_angle)
|
||||
Spi.write(Register.PWM_1_CTRL, 1, 4)
|
||||
elif port == 2:
|
||||
Spi.write(Register.SERVO_2_PWM_H, 2, mapped_angle)
|
||||
Spi.write(Register.PWM_1_CTRL, 1, 4)
|
||||
elif port == 3:
|
||||
Spi.write(Register.SERVO_3_PWM_H, 2, mapped_angle)
|
||||
Spi.write(Register.PWM_2_CTRL, 1, 4)
|
||||
elif port == 4:
|
||||
Spi.write(Register.SERVO_4_PWM_H, 2, mapped_angle)
|
||||
Spi.write(Register.PWM_2_CTRL, 1, 4)
|
||||
elif port == 5:
|
||||
Spi.write(Register.SERVO_5_PWM_H, 2, mapped_angle)
|
||||
Spi.write(Register.PWM_3_CTRL, 1, 4)
|
||||
elif port == 6:
|
||||
Spi.write(Register.SERVO_6_PWM_H, 2, mapped_angle)
|
||||
Spi.write(Register.PWM_3_CTRL, 1, 4)
|
||||
elif port == 7:
|
||||
Spi.write(Register.SERVO_7_PWM_H, 2, mapped_angle)
|
||||
Spi.write(Register.PWM_4_CTRL, 1, 4)
|
||||
elif port == 8:
|
||||
Spi.write(Register.SERVO_8_PWM_H, 2, mapped_angle)
|
||||
Spi.write(Register.PWM_4_CTRL, 1, 4)
|
||||
|
||||
@staticmethod
|
||||
def setup_position():
|
||||
"""Set position of servos to the position used during the setup process
|
||||
"""
|
||||
|
||||
for i in range(1, SERVO_COUNT + 1):
|
||||
Servo.set_position(i, 0)
|
|
@ -1,222 +0,0 @@
|
|||
import importlib
|
||||
from threading import Thread
|
||||
import multiprocessing
|
||||
from enum import IntEnum
|
||||
import time
|
||||
import sys
|
||||
import random
|
||||
|
||||
from compLib.LogstashLogging import Logging
|
||||
|
||||
SPI_BUS = 1
|
||||
SPI_DEVICE = 2
|
||||
SPI_SPEED = 4000000
|
||||
SPI_BUFFER_SIZE = 32
|
||||
SPI_HEALTH = True
|
||||
|
||||
# For development purposes
|
||||
spi_found = importlib.util.find_spec("spidev") is not None
|
||||
spi = None
|
||||
spi_lock = multiprocessing.Lock()
|
||||
last_spi_call = time.time()
|
||||
if spi_found:
|
||||
import spidev
|
||||
spi = spidev.SpiDev()
|
||||
spi.open(SPI_BUS, SPI_DEVICE)
|
||||
spi.max_speed_hz = SPI_SPEED
|
||||
spi.mode = 0
|
||||
spi.bits_per_word = 8
|
||||
|
||||
class Register(IntEnum):
|
||||
IDENTIFICATION_MODEL_ID = 1,
|
||||
IDENTIFICATION_MODEL_REV_MAJOR = 2,
|
||||
IDENTIFICATION_MODEL_REV_MINOR = 3,
|
||||
IDENTIFICATION_MODEL_REV_PATCH = 4,
|
||||
|
||||
# Motor encoder positions
|
||||
MOTOR_1_POS_B3 = 10,
|
||||
MOTOR_1_POS_B2 = 11,
|
||||
MOTOR_1_POS_B1 = 12,
|
||||
MOTOR_1_POS_B0 = 13,
|
||||
MOTOR_2_POS_B3 = 14,
|
||||
MOTOR_2_POS_B2 = 15,
|
||||
MOTOR_2_POS_B1 = 16,
|
||||
MOTOR_2_POS_B0 = 17,
|
||||
MOTOR_3_POS_B3 = 18,
|
||||
MOTOR_3_POS_B2 = 19,
|
||||
MOTOR_3_POS_B1 = 20,
|
||||
MOTOR_3_POS_B0 = 21,
|
||||
MOTOR_4_POS_B3 = 22,
|
||||
MOTOR_4_POS_B2 = 23,
|
||||
MOTOR_4_POS_B1 = 24,
|
||||
MOTOR_4_POS_B0 = 25,
|
||||
|
||||
# PWM Control Modes
|
||||
PWM_1_CTRL = 26,
|
||||
PWM_2_CTRL = 27,
|
||||
PWM_3_CTRL = 28,
|
||||
PWM_4_CTRL = 29,
|
||||
|
||||
# Motor pwm speed
|
||||
MOTOR_1_PWM_H = 30,
|
||||
MOTOR_1_PWM_L = 31,
|
||||
MOTOR_2_PWM_H = 32,
|
||||
MOTOR_2_PWM_L = 33,
|
||||
MOTOR_3_PWM_H = 34,
|
||||
MOTOR_3_PWM_L = 35,
|
||||
MOTOR_4_PWM_H = 36,
|
||||
MOTOR_4_PWM_L = 37,
|
||||
|
||||
# Servo goal position
|
||||
SERVO_1_PWM_H = 38,
|
||||
SERVO_1_PWM_L = 39,
|
||||
SERVO_2_PWM_H = 40,
|
||||
SERVO_2_PWM_L = 41,
|
||||
SERVO_3_PWM_H = 42,
|
||||
SERVO_3_PWM_L = 43,
|
||||
SERVO_4_PWM_H = 44,
|
||||
SERVO_4_PWM_L = 45,
|
||||
SERVO_5_PWM_H = 46,
|
||||
SERVO_5_PWM_L = 47,
|
||||
SERVO_6_PWM_H = 48,
|
||||
SERVO_6_PWM_L = 49,
|
||||
SERVO_7_PWM_H = 50,
|
||||
SERVO_7_PWM_L = 51,
|
||||
SERVO_8_PWM_H = 52,
|
||||
SERVO_8_PWM_L = 53,
|
||||
|
||||
# IR Sensor value
|
||||
IR_1_H = 54,
|
||||
IR_1_L = 55,
|
||||
IR_2_H = 56,
|
||||
IR_2_L = 57,
|
||||
IR_3_H = 58,
|
||||
IR_3_L = 59,
|
||||
IR_4_H = 60,
|
||||
IR_4_L = 61,
|
||||
IR_5_H = 62,
|
||||
IR_5_L = 63,
|
||||
IR_1_LED = 64,
|
||||
IR_2_LED = 65,
|
||||
IR_3_LED = 66,
|
||||
IR_4_LED = 67,
|
||||
IR_5_LED = 68,
|
||||
|
||||
# Display registers
|
||||
DISPLAY_LINE_1_C0 = 69,
|
||||
DISPLAY_LINE_2_C0 = 85,
|
||||
DISPLAY_LINE_3_C0 = 101,
|
||||
DISPLAY_LINE_4_C0 = 117
|
||||
|
||||
|
||||
class Spi(object):
|
||||
|
||||
@staticmethod
|
||||
def transfer(tx_buffer: list):
|
||||
if not spi_found:
|
||||
return [] * SPI_BUFFER_SIZE
|
||||
|
||||
write_reg = tx_buffer[1]
|
||||
tx_buffer_copy = tx_buffer.copy()
|
||||
|
||||
with spi_lock:
|
||||
spi.xfer(tx_buffer)
|
||||
rx_buffer = spi.xfer([0] * SPI_BUFFER_SIZE)
|
||||
|
||||
if rx_buffer[1] != write_reg:
|
||||
Logging.get_logger().error(f"Warning! SPI error during read/write of register {write_reg}! Retrying automagically")
|
||||
time.sleep(random.uniform(0.0, 0.1))
|
||||
return Spi.transfer(tx_buffer_copy)
|
||||
|
||||
global last_spi_call
|
||||
last_spi_call = time.time()
|
||||
|
||||
return rx_buffer
|
||||
|
||||
@staticmethod
|
||||
def read(reg: int, length: int):
|
||||
return int.from_bytes(Spi.read_array(reg, length), byteorder='big', signed=False)
|
||||
|
||||
@staticmethod
|
||||
def read_array(reg: int, length: int):
|
||||
if not type(reg) is int:
|
||||
reg = int(reg)
|
||||
|
||||
tx_buf = [0] * SPI_BUFFER_SIZE
|
||||
|
||||
tx_buf[0] = 0
|
||||
tx_buf[1] = reg
|
||||
tx_buf[2] = length
|
||||
|
||||
rx_buf = Spi.transfer(tx_buf)
|
||||
return rx_buf[2:2 + length]
|
||||
|
||||
@staticmethod
|
||||
def write(reg: int, length: int, value: int):
|
||||
if not type(reg) is int:
|
||||
reg = int(reg)
|
||||
|
||||
tx_buf = [0] * SPI_BUFFER_SIZE
|
||||
|
||||
tx_buf[0] = 1
|
||||
tx_buf[1] = reg
|
||||
tx_buf[2] = length
|
||||
|
||||
pos = 3
|
||||
for i in value.to_bytes(length, 'big'):
|
||||
tx_buf[pos] = i
|
||||
pos += 1
|
||||
|
||||
rx_buf = Spi.transfer(tx_buf)
|
||||
return int.from_bytes(rx_buf[2:2 + length], byteorder='big', signed=False)
|
||||
|
||||
@staticmethod
|
||||
def write_array(reg: int, length: int, values):
|
||||
if not type(reg) is int:
|
||||
reg = int(reg)
|
||||
|
||||
tx_buf = [0] * SPI_BUFFER_SIZE
|
||||
|
||||
tx_buf[0] = 1
|
||||
tx_buf[1] = reg
|
||||
tx_buf[2] = length
|
||||
|
||||
pos = 3
|
||||
for i in values:
|
||||
tx_buf[pos] = i
|
||||
pos += 1
|
||||
|
||||
rx_buf = Spi.transfer(tx_buf)
|
||||
return rx_buf
|
||||
|
||||
@staticmethod
|
||||
def health_check():
|
||||
if Spi.read(Register.IDENTIFICATION_MODEL_ID, 1) != 3:
|
||||
Logging.get_logger().error(f"Unable to read Version! Make sure the mainboard is connected!")
|
||||
print("Unable to read Version! Make sure the mainboard is connected!")
|
||||
|
||||
@staticmethod
|
||||
def start_health_check_loop():
|
||||
health_check_thread = Thread(target=Spi.health_check_loop)
|
||||
health_check_thread.setDaemon(True)
|
||||
health_check_thread.start()
|
||||
|
||||
@staticmethod
|
||||
def disable_health_check():
|
||||
global SPI_HEALTH
|
||||
SPI_HEALTH = False
|
||||
|
||||
@staticmethod
|
||||
def health_check_loop():
|
||||
while SPI_HEALTH:
|
||||
if last_spi_call + 0.5 < time.time():
|
||||
Spi.health_check()
|
||||
time.sleep(0.1)
|
||||
|
||||
@staticmethod
|
||||
def get_version():
|
||||
major = Spi.read(Register.IDENTIFICATION_MODEL_REV_MAJOR, 1)
|
||||
minor = Spi.read(Register.IDENTIFICATION_MODEL_REV_MINOR, 1)
|
||||
patch = Spi.read(Register.IDENTIFICATION_MODEL_REV_PATCH, 1)
|
||||
|
||||
return f"{major}.{minor}.{patch}"
|
|
@ -1,207 +0,0 @@
|
|||
import os
|
||||
import queue
|
||||
import socket
|
||||
import threading
|
||||
|
||||
import cv2
|
||||
from flask import Flask, Response
|
||||
|
||||
from compLib.LogstashLogging import Logging
|
||||
|
||||
RTMP_SERVER = os.getenv("RTMP_SERVER", "rtmp://localhost/live/stream")
|
||||
SERVE_VIDEO = os.getenv("SERVER_SRC", "/live")
|
||||
BUILDING_DOCS = os.getenv("BUILDING_DOCS", "false")
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
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 __Streaming:
|
||||
"""
|
||||
Class that handles rtmp streaming for opencv.
|
||||
|
||||
DO NOT CREATE AN INSTANCE OF THIS CLASS YOURSELF!
|
||||
|
||||
This is automatically done when importing this module. Use Vision.Streaming which is
|
||||
an instance of this class!
|
||||
|
||||
grab frames -> do your own processing -> publish frame -> view on http server
|
||||
"""
|
||||
|
||||
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=5)
|
||||
t = threading.Thread(target=self._reader)
|
||||
t.daemon = True
|
||||
t.start()
|
||||
|
||||
def _reader(self):
|
||||
while True:
|
||||
ret, frame = self.cap.read()
|
||||
if not ret:
|
||||
break
|
||||
if not self.q.empty():
|
||||
try:
|
||||
self.q.get_nowait()
|
||||
except queue.Empty:
|
||||
pass
|
||||
self.q.put(frame)
|
||||
|
||||
def read(self):
|
||||
return self.q.get()
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Create instance of __Streaming class
|
||||
|
||||
This is done implicitly when importing the vision module and will only fail if you would
|
||||
create an object of this class. (There can (SHOULD!) only be one VideCapture)
|
||||
"""
|
||||
Logging.get_logger().info("capturing rtmp stream is disabled in this version")
|
||||
#self.__camera_stream = cv2.VideoCapture(RTMP_SERVER)
|
||||
#self.__camera_stream = cv2.VideoCapture(0)
|
||||
#self.__camera_stream.set(3, 640)
|
||||
#self.__camera_stream.set(4, 480)
|
||||
self.__camera_stream = self.__NoBufferVideoCapture(0)
|
||||
self.__newest_frame = None
|
||||
self.__lock = threading.Lock()
|
||||
Logging.get_logger().info("Initialized vision")
|
||||
|
||||
def get_frame(self):
|
||||
"""
|
||||
Grab the newest frame from the rtmp stream.
|
||||
|
||||
:return: An opencv frame
|
||||
"""
|
||||
#ret, img16 = self.__camera_stream.read()
|
||||
img16 = self.__camera_stream.read()
|
||||
return img16
|
||||
|
||||
def publish_frame(self, image):
|
||||
"""
|
||||
Publish an opencv frame to the http webserver.
|
||||
|
||||
:param image: Opencv frame that will be published
|
||||
: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')
|
||||
|
||||
|
||||
Streaming = None
|
||||
if BUILDING_DOCS == "false":
|
||||
# instantiate private class __Streaming
|
||||
Streaming = __Streaming()
|
||||
Logging.get_logger().info("created instance of streaming class")
|
||||
|
||||
|
||||
@app.route("/live")
|
||||
def __video_feed():
|
||||
"""
|
||||
Define route for serving jpeg stream.
|
||||
|
||||
:return: Return the response generated along with the specific media.
|
||||
"""
|
||||
return Response(Streaming._newest_frame_generator(),
|
||||
mimetype="multipart/x-mixed-replace; boundary=frame")
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def __index():
|
||||
"""
|
||||
Define route for serving a static http site to view the stream.
|
||||
|
||||
:return: Static html page
|
||||
"""
|
||||
return HTML
|
||||
|
||||
|
||||
def get_ip():
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
try:
|
||||
s.connect(('10.255.255.255', 1))
|
||||
IP = s.getsockname()[0]
|
||||
except Exception as e:
|
||||
IP = '127.0.0.1'
|
||||
print(f"Error could not query ip: {e}")
|
||||
finally:
|
||||
s.close()
|
||||
return IP
|
||||
|
||||
|
||||
def __start_flask():
|
||||
"""
|
||||
Function for running flask server in a thread.
|
||||
|
||||
:return:
|
||||
"""
|
||||
Logging.get_logger().info("starting flask server")
|
||||
app.run(host="0.0.0.0", port=9898, debug=True, threaded=True, use_reloader=False)
|
||||
ip = get_ip()
|
||||
Logging.get_logger().info(f"Vision stream started and can be viewed on: {ip}:9898")
|
||||
print(f"\033[92mVision stream started and can be viewed on: {ip}:9898\033[0m")
|
||||
|
||||
|
||||
if BUILDING_DOCS == "false":
|
||||
# start flask service in the background
|
||||
__webserver_thread = threading.Thread(target=__start_flask)
|
||||
__webserver_thread.start()
|
||||
|
||||
# for debugging and testing start processing frames and detecting a 6 by 9 calibration chessboard
|
||||
if __name__ == '__main__' and BUILDING_DOCS == "false":
|
||||
while True:
|
||||
frame = Streaming.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)
|
||||
Streaming.publish_frame(frame)
|
|
@ -1,50 +0,0 @@
|
|||
__version__ = "0.4.1-1"
|
||||
|
||||
import sys
|
||||
import importlib
|
||||
import compLib.LogstashLogging
|
||||
import compLib.MetricsLogging
|
||||
from compLib.Lock import Lock
|
||||
|
||||
apt_found = importlib.util.find_spec("apt") is not None
|
||||
spi_found = importlib.util.find_spec("spidev") is not None
|
||||
|
||||
|
||||
if apt_found:
|
||||
import apt
|
||||
|
||||
try:
|
||||
__versions = apt.Cache()["python3-complib"].versions
|
||||
if len(__versions) != 1:
|
||||
print(f"Starting compLib! \033[91mVersion: {__version__} is outdated\033[0m\n"
|
||||
f"\033[92m[!] run the command 'sudo apt update && sudo apt install python3-complib' to install the newest version\033[0m")
|
||||
else:
|
||||
print(f"Starting compLib! \033[92mVersion: {__version__} is up to date\033[0m")
|
||||
except Exception as e:
|
||||
compLib.LogstashLogging.Logging.get_logger().error(f"error during checking apt package version -> {str(e)}")
|
||||
print(f"\033[91merror during checking apt package version -> {str(e)}\033[0m\n")
|
||||
else:
|
||||
print("apt is not installed! This is for local development only!")
|
||||
|
||||
if Lock.is_locked():
|
||||
print("Another program using compLib is still running! Delete /root/complib.lock if this is an error. Exiting...")
|
||||
sys.exit()
|
||||
else:
|
||||
Lock.lock()
|
||||
|
||||
if spi_found:
|
||||
from compLib.Spi import Spi
|
||||
from compLib.Reset import Reset
|
||||
from compLib.Encoder import Encoder
|
||||
from compLib.Motor import Motor
|
||||
import logging
|
||||
|
||||
print(f"\033[34mInitializing chipmunk board...\033[0m")
|
||||
Reset.reset_bot()
|
||||
Spi.health_check()
|
||||
Spi.start_health_check_loop()
|
||||
Motor.start_robot_state_loop()
|
||||
Encoder.clear_all()
|
||||
print(f"\033[34mReady\033[0m\n")
|
||||
else:
|
||||
print("spidev is not installed! This is for local development only!")
|
|
@ -1,11 +0,0 @@
|
|||
[Unit]
|
||||
Description=Monitoring service
|
||||
[Service]
|
||||
ExecStart=/usr/bin/python3 /usr/local/lib/python3.7/dist-packages/compLib/IPService.py
|
||||
Environment="debug=False"
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
Type=notify
|
||||
[Install]
|
||||
Alias=complib
|
||||
WantedBy=default.target
|
2
client_s1/docs/.gitignore
vendored
2
client_s1/docs/.gitignore
vendored
|
@ -1,2 +0,0 @@
|
|||
build
|
||||
logs.db
|
|
@ -1,19 +0,0 @@
|
|||
.. image:: images/compair-logo-white.svg
|
||||
|
||||
Competition Robot Library
|
||||
#############################
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Contents:
|
||||
|
||||
Contents
|
||||
*********
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 5
|
||||
:glob:
|
||||
|
||||
self
|
||||
other/usage
|
||||
lib/*
|
|
@ -1,107 +0,0 @@
|
|||
.. _lib_api:
|
||||
|
||||
Api
|
||||
****
|
||||
|
||||
Seeding
|
||||
========
|
||||
|
||||
.. autoclass:: compLib.Api.Seeding
|
||||
:members:
|
||||
|
||||
Double Elimination
|
||||
===================
|
||||
|
||||
.. autoclass:: compLib.Api.DoubleElim
|
||||
:members:
|
||||
|
||||
Position
|
||||
========
|
||||
|
||||
.. autoclass:: compLib.Api.Position
|
||||
:members:
|
||||
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Calling Seeding API
|
||||
---------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from compLib.Api import Seeding
|
||||
|
||||
zones, code = Seeding.get_delivery()
|
||||
if code == 403:
|
||||
print(f"I am not in the correct zone to make that request!")
|
||||
else:
|
||||
print(f"First we need to go to zone {zone[0]}")
|
||||
# put code here to follow line and drive to the zone
|
||||
print(f"Now we need to go to zone {zone[1]}")
|
||||
# put code here to follow line and drive to the next zone
|
||||
print(f"Now we need to go to zone {zone[2]}")
|
||||
# put code here to follow line and drive to the last zone
|
||||
print(f"We delivered all packages, hopefully we scored some points!")
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from compLib.Api import Seeding
|
||||
|
||||
package, code = Seeding.get_cargo("yellow")
|
||||
if code == 403:
|
||||
print(f"I am not in the correct zone to make that request!")
|
||||
elif code == 404:
|
||||
print(f"I am in the correct zone, but there is no yellow package here.")
|
||||
elif code == 413:
|
||||
print(f"I am in the correct zone, but I already have two packages loaded.")
|
||||
else code == 200:
|
||||
print(f"The {package['color']} has been picked up!")
|
||||
|
||||
Calling Double Elimination API
|
||||
----------------------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from compLib.Api import DoubleElim
|
||||
|
||||
position, status = DoubleElim.get_position()
|
||||
print(f"Position of my robot is: x={position.x}, y={position.y} and rotation is: {position.degrees}, the server responded with status code: {status}")
|
||||
|
||||
goal, status = DoubleElim.get_goal()
|
||||
print(f"Goal is at: x={goal.x}, y={goal.y}, the server responded with status code: {status}")
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from compLib.Api import DoubleElim
|
||||
import time
|
||||
|
||||
# function which waits for the game to be started (you should include this in your double elimination program)
|
||||
def wait_for_start():
|
||||
_, status = DoubleElim.get_position()
|
||||
while status == 503:
|
||||
time.sleep(0.1)
|
||||
_, status = DoubleElim.get_position()
|
||||
|
||||
wait_for_start()
|
||||
print(f"Game has started, lets score some points!!")
|
||||
|
||||
position, status = DoubleElim.get_position()
|
||||
print(f"Position of my robot is: x={position.x}, y={position.y} and rotation is: {position.degrees}, the server responded with status code: {status}")
|
||||
|
||||
opponent_position, status = DoubleElim.get_opponent()
|
||||
print(f"Position of the opponents robot is: x={opponent_position.x}, y={opponent_position.y} and rotation is: {opponent_position.degrees}, the server responded with status code: {status}")
|
||||
|
||||
goal, status = DoubleElim.get_goal()
|
||||
print(f"Goal is at: x={goal.x}, y={goal.y}, the server responded with status code: {status}")
|
||||
|
||||
items, status = DoubleElim.get_items()
|
||||
print(f"There are currently {len(items)} on the gameboard: {items}, the server responded with status code: {status}")
|
||||
|
||||
score, status = DoubleElim.get_score()
|
||||
print(f"The current score of the game is {score}, the server responded with status code: {status}")
|
||||
|
||||
meteoroids, status = DoubleElim.get_meteoroids()
|
||||
print(f"The current meteoroids in the game are {meteoroids}, the server responded with status code: {status}")
|
||||
|
||||
In this second example we wait until the game is started by the judges and then make all possible requests once. You should use the wait_for_start function in your double elimination program. If your robot starts too soon your run will not count!
|
|
@ -1,83 +0,0 @@
|
|||
.. _lib_vision:
|
||||
|
||||
Aruco
|
||||
*******
|
||||
|
||||
Examples
|
||||
=========
|
||||
|
||||
Recognizing ArUco tags
|
||||
-------------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import time
|
||||
import cv2
|
||||
from cv2 import aruco
|
||||
from compLib import Vision
|
||||
|
||||
ARUCO_DICT = cv2.aruco.Dictionary_get(aruco.DICT_6X6_250)
|
||||
ARUCO_PARAMETERS = aruco.DetectorParameters_create()
|
||||
|
||||
def getTagCenterFromFrame(id, frame):
|
||||
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
||||
|
||||
corners, ids, rejectedImgPoints = aruco.detectMarkers(gray, ARUCO_DICT, parameters = ARUCO_PARAMETERS)
|
||||
frame = aruco.drawDetectedMarkers(frame.copy(), corners, ids)
|
||||
|
||||
if ids is None:
|
||||
return frame, None, None
|
||||
|
||||
for tag_id, corner in zip(ids, corners):
|
||||
if (tag_id[0] == id):
|
||||
|
||||
x, y = 0, 0
|
||||
for i in range(4):
|
||||
x += corner[0][i][0] * 0.25
|
||||
y += corner[0][i][1] * 0.25
|
||||
|
||||
return frame, x, y
|
||||
return frame, None, None
|
||||
|
||||
# Get the center from the aruco tag with the specified id
|
||||
# in pixel coordinates (0-640, 0-480)
|
||||
def getTagPosition(id):
|
||||
frame = Vision.Streaming.get_frame()
|
||||
frame, x, y = getTagCenterFromFrame(id, frame)
|
||||
Vision.Streaming.publish_frame(frame)
|
||||
return x, y
|
||||
|
||||
# Get the normalized center coordinates from the aruco tag
|
||||
# with the specified id
|
||||
# left is -1, right +1
|
||||
# bottom is -1, top +1
|
||||
def getNormalizedTagPosition(id):
|
||||
frame = Vision.Streaming.get_frame()
|
||||
frame, x, y = getTagCenterFromFrame(id, frame)
|
||||
Vision.Streaming.publish_frame(frame)
|
||||
|
||||
if x is None or y is None:
|
||||
return None, None
|
||||
|
||||
height, width = frame.shape[:2]
|
||||
x = x / width * 2.0 - 1.0
|
||||
y = -(y / height * 2.0 - 1.0)
|
||||
return x, y
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
desiredID = 11
|
||||
|
||||
while True:
|
||||
x, y = getNormalizedTagPosition(desiredID)
|
||||
|
||||
if x is not None:
|
||||
print("X Coordinate: ", x)
|
||||
else:
|
||||
print("Tag not found")
|
||||
|
||||
|
||||
This example shows how to recognize ArUco tags based on their id and position.
|
||||
You can specify an ID of the tag you want to use and if it's found, the coordinates of the center are returned.
|
||||
With the normalized function this is very easy: The x-coordinate is -1 on the left, 1 on the right and 0 in the center of the screen, same for y.
|
||||
This way it is quite simple to act on the position of the tag.
|
|
@ -1,23 +0,0 @@
|
|||
.. _lib_display:
|
||||
|
||||
Display
|
||||
*******
|
||||
|
||||
Class Documentation
|
||||
====================
|
||||
|
||||
.. autoclass:: compLib.Display.Display
|
||||
:members:
|
||||
|
||||
Examples
|
||||
=========
|
||||
|
||||
Write a line to the display
|
||||
---------------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import time
|
||||
from compLib.Display import Display
|
||||
|
||||
Display.write(1, "Hello World!")
|
|
@ -1,20 +0,0 @@
|
|||
.. _lib_irsensor:
|
||||
|
||||
Infrared Sensor
|
||||
****************
|
||||
|
||||
.. autoclass:: compLib.IRSensor.IRSensor
|
||||
:members:
|
||||
|
||||
Examples
|
||||
=========
|
||||
|
||||
Testing analog sensors
|
||||
-------------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from compLib import IRSensor
|
||||
|
||||
while True:
|
||||
print ("left: {} middle: {} right: {}".format(IRSensor.read(1), IRSensor.read(3), IRSensor.read(5)))
|
|
@ -1,79 +0,0 @@
|
|||
.. _lib_Linefollower:
|
||||
|
||||
Linefollower Examples
|
||||
*********************
|
||||
|
||||
Simple Linefollower
|
||||
-------------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from compLib.Motor import Motor
|
||||
from compLib.Display import Display
|
||||
from compLib.IRSensor import IRSensor
|
||||
from compLib.Encoder import Encoder
|
||||
|
||||
import time
|
||||
|
||||
IRSensor.set(1, True)
|
||||
IRSensor.set(2, True)
|
||||
IRSensor.set(3, True)
|
||||
IRSensor.set(4, True)
|
||||
IRSensor.set(5, True)
|
||||
|
||||
DRIVE_SPEED = 75
|
||||
COLOR_BREAK = 900
|
||||
KP = 10.0
|
||||
KD = 0.0
|
||||
|
||||
def drive(leftSpeed, rightSpeed):
|
||||
rightSpeed *= -0.906
|
||||
|
||||
Motor.power(1, min(max(-100, rightSpeed), 100))
|
||||
Motor.power(4, min(max(-100, leftSpeed), 100))
|
||||
|
||||
def follow(sleepTime = 0.1):
|
||||
lastError = 0
|
||||
sensorsBlack = 0
|
||||
while sensorsBlack < 3:
|
||||
sensorsBlack = 0
|
||||
for i in range(1, 6):
|
||||
if IRSensor.read(i) > COLOR_BREAK:
|
||||
sensorsBlack += 1
|
||||
|
||||
error = lastError
|
||||
if IRSensor.read(3) > COLOR_BREAK:
|
||||
error = 0
|
||||
elif IRSensor.read(1) > COLOR_BREAK:
|
||||
error = -1.5
|
||||
elif IRSensor.read(5) > COLOR_BREAK:
|
||||
error = 1.5
|
||||
elif IRSensor.read(2) > COLOR_BREAK:
|
||||
error = -1
|
||||
elif IRSensor.read(4) > 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)
|
||||
|
||||
def main():
|
||||
follow()
|
||||
follow()
|
||||
follow()
|
||||
follow()
|
||||
follow(0.2)
|
||||
|
||||
main()
|
|
@ -1,22 +0,0 @@
|
|||
.. _lib_logging:
|
||||
|
||||
Logging
|
||||
*******
|
||||
|
||||
Class Documentation
|
||||
====================
|
||||
|
||||
.. autoclass:: compLib.LogstashLogging.Logging
|
||||
:members:
|
||||
|
||||
Examples
|
||||
=========
|
||||
|
||||
Turn up the logging
|
||||
--------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from compLib.LogstashLogging import Logging
|
||||
|
||||
Logging.set_debug()
|
|
@ -1,23 +0,0 @@
|
|||
.. _lib_motor:
|
||||
|
||||
Motor
|
||||
******
|
||||
|
||||
Class Documentation
|
||||
====================
|
||||
|
||||
.. autoclass:: compLib.Motor.Motor
|
||||
:members:
|
||||
|
||||
Examples
|
||||
=========
|
||||
|
||||
Driving straight (maybe)
|
||||
-------------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from compLib.Motor import Motor
|
||||
|
||||
Motor.power(1, -50)
|
||||
Motor.power(4, 50)
|
|
@ -1,50 +0,0 @@
|
|||
.. _lib_odom:
|
||||
|
||||
Odometry
|
||||
********
|
||||
|
||||
Class Documentation
|
||||
====================
|
||||
|
||||
.. autoclass:: compLib.Odom.Odometry
|
||||
:members:
|
||||
|
||||
.. autoclass:: compLib.Odom.Odom
|
||||
:members:
|
||||
|
||||
|
||||
Examples
|
||||
=========
|
||||
|
||||
Getting actual distance driven
|
||||
------------------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import time
|
||||
import math
|
||||
from compLib.Motor import Motor
|
||||
from compLib.Encoder import Encoder
|
||||
from compLib.Odom import Odom, Odometry
|
||||
|
||||
# distance in meters
|
||||
# speed in % of max speed
|
||||
def drive_example(distance, speed):
|
||||
Odom.update()
|
||||
odom = Odom.get_odom()
|
||||
while abs(odom.get_x()) < distance:
|
||||
Odom.update()
|
||||
odom = Odom.get_odom()
|
||||
|
||||
Motor.power(4, speed)
|
||||
Motor.power(1, -speed)
|
||||
|
||||
print(f" Forward: {odom.get_x()} m")
|
||||
print(f" Right: {odom.get_y()} m")
|
||||
print(f" Turned: {math.degrees(odom.get_orientation())} degrees")
|
||||
|
||||
Motor.active_break(1)
|
||||
Motor.active_break(4)
|
||||
time.sleep(0.1)
|
||||
Encoder.clear_all()
|
||||
Odom.clear()
|
|
@ -1,71 +0,0 @@
|
|||
.. _lib_qc:
|
||||
|
||||
Quality Control
|
||||
###############
|
||||
|
||||
Infrared Test
|
||||
-------------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from compLib.IRSensor import IRSensor
|
||||
import time
|
||||
|
||||
IRSensor.set(1, True)
|
||||
IRSensor.set(2, True)
|
||||
IRSensor.set(3, True)
|
||||
IRSensor.set(4, True)
|
||||
IRSensor.set(5, True)
|
||||
|
||||
while True:
|
||||
t = time.time()
|
||||
for i in range(1, 6):
|
||||
print(f"{i}: {IRSensor.read(i)}")
|
||||
print("")
|
||||
time.sleep(0.2)
|
||||
|
||||
Motor Test
|
||||
-------------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from compLib.Motor import Motor
|
||||
from compLib.Encoder import Encoder
|
||||
import time
|
||||
|
||||
Motor.power(1, -50)
|
||||
Motor.power(4, 50)
|
||||
|
||||
while True:
|
||||
print(f"L:{Encoder.read(4)} R:{Encoder.read(1)}")
|
||||
time.sleep(0.1)
|
||||
|
||||
Servo Test
|
||||
-------------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from compLib.Servo import Servo
|
||||
import time
|
||||
|
||||
for i in range(1, 8 + 1):
|
||||
Servo.set_position(i, 45)
|
||||
print(f"{i}")
|
||||
time.sleep(1)
|
||||
|
||||
Servo.setup_position()
|
||||
time.sleep(10)
|
||||
|
||||
Vision Test
|
||||
-------------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import cv2
|
||||
from compLib import Vision
|
||||
from compLib.Servo import Servo
|
||||
|
||||
while True:
|
||||
frame = Vision.Streaming.get_frame()
|
||||
Vision.Streaming.publish_frame(frame)
|
||||
Servo.set_position(3, -45)
|
|
@ -1,11 +0,0 @@
|
|||
.. _lib_robot:
|
||||
|
||||
Robot
|
||||
******
|
||||
|
||||
Class Documentation
|
||||
====================
|
||||
|
||||
.. autoclass:: compLib.Robot.Robot
|
||||
:members:
|
||||
:private-members:
|
|
@ -1,7 +0,0 @@
|
|||
.. _lib_servo:
|
||||
|
||||
Servo
|
||||
******
|
||||
|
||||
.. autoclass:: compLib.Servo.Servo
|
||||
:members:
|
|
@ -1,104 +0,0 @@
|
|||
.. _lib_vision:
|
||||
|
||||
Vision
|
||||
*******
|
||||
|
||||
This module provides an interface for grabbing an rtmp stream and using the images to do some processing in opencv.
|
||||
|
||||
How do I use this module?
|
||||
|
||||
1. Get frames from the raspberry pi camera
|
||||
2. -- here comes your own processing --
|
||||
3. Publish the processed frames on an http server
|
||||
4. You can view the http stream of your processed images in a web browser
|
||||
|
||||
Opencv Stream
|
||||
==============
|
||||
|
||||
Because of the rtmp stream needing to buffer some frames and waiting for P-Frames, importing this module might take up
|
||||
to 5 Seconds.
|
||||
|
||||
.. autoclass:: compLib.Vision.__Streaming
|
||||
:members:
|
||||
|
||||
Examples
|
||||
=========
|
||||
|
||||
Using the Vision Module
|
||||
-------------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import cv2
|
||||
from compLib import Vision
|
||||
|
||||
while True:
|
||||
# get newest opencv frame from camera
|
||||
frame = Vision.Streaming.get_frame()
|
||||
|
||||
# do some processing with the frame.....
|
||||
|
||||
# publish frame to streaming server
|
||||
Vision.Streaming.publish_frame(frame)
|
||||
|
||||
Connect the raspberry pi to your internet and view the stream at: "http://your_raspi_ip:9898/". This should display
|
||||
your raspberry pi camera. Note: the stream will lag a little bit BUT the processing of the image will be done in
|
||||
realtime.
|
||||
|
||||
The output on the website should show whatever your raspberry pi cam records:
|
||||
|
||||
.. image:: images/opencv_http_stream.png
|
||||
:width: 680
|
||||
:alt: Processed frames from opencv
|
||||
|
||||
|
||||
Chessboard Detection
|
||||
------------------------------------------
|
||||
|
||||
In this example we process the captured stream of images and want to detect chessboards. Run this example and
|
||||
point your raspberry pi camera to a chessboard and it should be detected.
|
||||
|
||||
For testing you can point it at this image:
|
||||
|
||||
.. image:: images/chessboard.jpg
|
||||
:width: 680
|
||||
:alt: Chessboard for opencv processing
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import cv2
|
||||
from compLib import Vision
|
||||
|
||||
while True:
|
||||
# get newest opencv frame from camera
|
||||
frame = Vision.Streaming.get_frame()
|
||||
|
||||
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
|
||||
|
||||
# convert image to grayscale image
|
||||
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
||||
|
||||
# find the chessboard corners
|
||||
ret, corners = cv2.findChessboardCorners(gray, (6, 9), None)
|
||||
|
||||
# draw detected chessboard position onto the image
|
||||
cv2.drawChessboardCorners(frame, (6, 9), corners, ret)
|
||||
|
||||
# publish frame to streaming server
|
||||
Vision.Streaming.publish_frame(frame)
|
||||
|
||||
Connect the raspberry pi to your internet and view the stream at: "http://your_raspi_ip:9898/".
|
||||
|
||||
The output image should look like this:
|
||||
|
||||
.. image:: images/chessboard_detected.jpg
|
||||
:width: 680
|
||||
:alt: Processed frames from opencv
|
||||
|
||||
Here is a screenshot of the stream website while viewing the chessboard in this documentation.
|
||||
|
||||
.. image:: images/opencv_processed.png
|
||||
:width: 680
|
||||
:alt: Processed frames from opencv
|
||||
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
.. _other_usage:
|
||||
|
||||
Usage
|
||||
######
|
||||
|
||||
.. 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()
|
|
@ -1,63 +0,0 @@
|
|||
grep -qxF "apt update" /etc/rc.local
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "adding apt update to rc.local"
|
||||
sed -i "2s/^/apt update\n/" /etc/rc.local
|
||||
fi
|
||||
|
||||
install_package() {
|
||||
echo "Installing package '$1' via pip3"
|
||||
pip3 install "$1"
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Successfully installed pip3 package '$1'"
|
||||
else
|
||||
echo "Could not install pip3 package '$1'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
#install_package "smbus"
|
||||
#install_package "requests"
|
||||
#install_package "flask"
|
||||
#install_package "python-logstash-async"
|
||||
#install_package "RPi.GPIO"
|
||||
#install_package "spidev"
|
||||
#install_package "influxdb_client"
|
||||
|
||||
pip3 install smbus requests flask python-logstash-async RPi.GPIO spidev influxdb_client filelock
|
||||
|
||||
echo "Setting up opencv4"
|
||||
pkg-config --modversion opencv4
|
||||
|
||||
echo "Setting up nginx rtmp server"
|
||||
sudo /etc/init.d/nginx start
|
||||
|
||||
{
|
||||
echo 'load_module "modules/ngx_rtmp_module.so";'
|
||||
echo 'worker_processes auto;'
|
||||
echo 'rtmp_auto_push on;'
|
||||
echo 'events {}'
|
||||
echo 'rtmp {'
|
||||
echo ' server {'
|
||||
echo ' listen 1935;'
|
||||
echo ' listen [::]:1935 ipv6only=on;'
|
||||
echo ' application live {'
|
||||
echo ' live on;'
|
||||
echo ' record off;'
|
||||
echo ' }'
|
||||
echo ' }'
|
||||
echo '}'
|
||||
} >| /etc/nginx/nginx.conf
|
||||
|
||||
echo "Starting pigpiod daemon"
|
||||
|
||||
sudo systemctl start pigpiod
|
||||
sudo systemctl enable pigpiod
|
||||
|
||||
base64 -d << UPD
|
||||
CiBfX19fX18gICAgIF9fX19fXyAgICAgX18gICAgX18gICAgIF9fX19fXyAgIF9fICAgICAgICAgX18gICAgIF9fX19fXyAgICAgICAgICAgICAgICAgIAovXCAgX19fXCAgIC9cICBfXyBcICAgL1wgIi0uLyAgXCAgIC9cICA9PSBcIC9cIFwgICAgICAgL1wgXCAgIC9cICA9PSBcICAgICAgICAgICAgICAgICAKXCBcIFxfX19fICBcIFwgXC9cIFwgIFwgXCBcLS4vXCBcICBcIFwgIF8tLyBcIFwgXF9fX18gIFwgXCBcICBcIFwgIF9fPCAgICAgICAgICAgICAgICAgCiBcIFxfX19fX1wgIFwgXF9fX19fXCAgXCBcX1wgXCBcX1wgIFwgXF9cICAgIFwgXF9fX19fXCAgXCBcX1wgIFwgXF9fX19fXCAgICAgICAgICAgICAgIAogIFwvX19fX18vICAgXC9fX19fXy8gICBcL18vICBcL18vICAgXC9fLyAgICAgXC9fX19fXy8gICBcL18vICAgXC9fX19fXy8gICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiBfXyAgX18gICAgIF9fX19fXyAgICAgIF9fX19fXyAgIF9fX19fXyAgICAgICAgX19fX18gICAgIF9fX19fXyAgICAgX19fX19fICAgX19fX19fICAgIAovXCBcL1wgXCAgIC9cICA9PSBcICAgIC9cX18gIF9cIC9cICBfXyBcICAgICAgL1wgIF9fLS4gIC9cICBfXyBcICAgL1xfXyAgX1wgL1wgIF9fX1wgICAKXCBcIFxfXCBcICBcIFwgIF8tLyAgICBcL18vXCBcLyBcIFwgXC9cIFwgICAgIFwgXCBcL1wgXCBcIFwgIF9fIFwgIFwvXy9cIFwvIFwgXCAgX19cICAgCiBcIFxfX19fX1wgIFwgXF9cICAgICAgICAgXCBcX1wgIFwgXF9fX19fXCAgICAgXCBcX19fXy0gIFwgXF9cIFxfXCAgICBcIFxfXCAgXCBcX19fX19cIAogIFwvX19fX18vICAgXC9fLyAgICAgICAgICBcL18vICAgXC9fX19fXy8gICAgICBcL19fX18vICAgXC9fL1wvXy8gICAgIFwvXy8gICBcL19fX19fLyA=
|
||||
UPD
|
||||
echo ""
|
||||
echo ""
|
||||
|
||||
service complib restart
|
||||
systemctl enable complib
|
|
@ -1,37 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
import setuptools
|
||||
import os
|
||||
|
||||
base_dir = os.path.dirname(__file__)
|
||||
readme_path = os.path.join(base_dir, "README.md")
|
||||
if os.path.exists(readme_path):
|
||||
with open(readme_path) as stream:
|
||||
long_description = stream.read()
|
||||
else:
|
||||
long_description = ""
|
||||
|
||||
setuptools.setup(
|
||||
name="complib",
|
||||
version="0.4.1-1",
|
||||
author="F-WuTs",
|
||||
author_email="--",
|
||||
description="",
|
||||
summary="Library for the competition",
|
||||
platforms=["any"],
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
url="https://github.com/F-WuTS/compLIB",
|
||||
packages=setuptools.find_packages(),
|
||||
include_package_data=True,
|
||||
classifiers=[
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"License :: Other/Proprietary License",
|
||||
"Operating System :: Unix"
|
||||
],
|
||||
license="proprietary"#,
|
||||
#install_requires=[
|
||||
# "smbus",
|
||||
# "requests",
|
||||
# "python-logstash-async"
|
||||
#]
|
||||
)
|
|
@ -1,61 +0,0 @@
|
|||
|
||||
import unittest
|
||||
|
||||
import os
|
||||
os.environ["API_URL"] = "http://127.0.0.1:5000/"
|
||||
from compLib.Api import Seeding
|
||||
from compLib.Api import DoubleElim
|
||||
|
||||
|
||||
class TestApiServer(unittest.TestCase):
|
||||
def test_all(self):
|
||||
ret, code = Seeding.get_garbage()
|
||||
print(ret)
|
||||
print(code)
|
||||
ret, code = Seeding.get_delivery()
|
||||
print(ret)
|
||||
print(code)
|
||||
ret, code = Seeding.get_material()
|
||||
print(ret)
|
||||
print(code)
|
||||
ret, code = Seeding.list_cargo()
|
||||
print(ret)
|
||||
print(code)
|
||||
ret, code = Seeding.get_cargo("green")
|
||||
print(ret)
|
||||
print(code)
|
||||
ret, code = Seeding.get_cargo("yellow")
|
||||
print(ret)
|
||||
print(code)
|
||||
ret, code = Seeding.get_cargo("blue")
|
||||
print(ret)
|
||||
print(code)
|
||||
ret, code = Seeding.get_cargo("red")
|
||||
print(ret)
|
||||
print(code)
|
||||
|
||||
|
||||
class TestApiServerDoubleElim(unittest.TestCase):
|
||||
def test_all(self):
|
||||
ret, code = DoubleElim.get_goal()
|
||||
print(ret)
|
||||
print(code)
|
||||
ret, code = DoubleElim.get_items()
|
||||
print(ret)
|
||||
print(code)
|
||||
ret, code = DoubleElim.get_scores()
|
||||
print(ret)
|
||||
print(code)
|
||||
ret, code = DoubleElim.get_opponent()
|
||||
print(ret)
|
||||
print(code)
|
||||
ret, code = DoubleElim.get_position()
|
||||
print(ret)
|
||||
print(code)
|
||||
ret, code = DoubleElim.get_meteoroids()
|
||||
print(ret)
|
||||
print(code)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
3
client_s2/.idea/.gitignore
generated
vendored
3
client_s2/.idea/.gitignore
generated
vendored
|
@ -1,3 +0,0 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
8
client_s2/.idea/client_s2.iml
generated
8
client_s2/.idea/client_s2.iml
generated
|
@ -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>
|
|
@ -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>
|
|
@ -1,6 +0,0 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
7
client_s2/.idea/misc.xml
generated
7
client_s2/.idea/misc.xml
generated
|
@ -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>
|
8
client_s2/.idea/modules.xml
generated
8
client_s2/.idea/modules.xml
generated
|
@ -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>
|
15
client_s2/.idea/saveactions_settings.xml
generated
15
client_s2/.idea/saveactions_settings.xml
generated
|
@ -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_s2/.idea/vcs.xml
generated
6
client_s2/.idea/vcs.xml
generated
|
@ -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>
|
|
@ -1 +0,0 @@
|
|||
include readme.md
|
|
@ -1,45 +0,0 @@
|
|||
import socket
|
||||
|
||||
import compLib.CompLib_pb2 as CompLib_pb2
|
||||
|
||||
UNIX_SOCKET_PATH = "/tmp/compLib"
|
||||
TCP_SOCKET_HOST = "192.168.0.151"
|
||||
TCP_SOCKET_PORT = 9090
|
||||
|
||||
|
||||
class CompLibClient(object):
|
||||
|
||||
@staticmethod
|
||||
def send(data: bytes, size: int) -> bytes:
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
||||
sock.connect((TCP_SOCKET_HOST, TCP_SOCKET_PORT))
|
||||
|
||||
# with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
|
||||
# sock.connect(UNIX_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(response_size)
|
||||
|
||||
response_bytes = sock.recv(response_size)
|
||||
# print(response_bytes.hex())
|
||||
# print(len(response_bytes))
|
||||
|
||||
CompLibClient.check_response(response_bytes)
|
||||
|
||||
return response_bytes
|
||||
|
||||
@staticmethod
|
||||
def check_response(response_bytes: bytes) -> bool:
|
||||
print(f"{response_bytes}")
|
||||
res = CompLib_pb2.GenericResponse()
|
||||
res.ParseFromString(response_bytes)
|
||||
|
||||
if res.status.successful:
|
||||
return True
|
||||
|
||||
# TODO: Log error message if unsuccessful
|
||||
return False
|
|
@ -1,220 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: CompLib.proto
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import message as _message
|
||||
from google.protobuf import reflection as _reflection
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\rCompLib.proto\x12\x07\x43ompLib\"\x1e\n\x06Header\x12\x14\n\x0cmessage_type\x18\x01 \x01(\t\"3\n\x06Status\x12\x12\n\nsuccessful\x18\x01 \x01(\x08\x12\x15\n\rerror_message\x18\x02 \x01(\t\"1\n\x0eGenericRequest\x12\x1f\n\x06header\x18\x01 \x01(\x0b\x32\x0f.CompLib.Header\"S\n\x0fGenericResponse\x12\x1f\n\x06header\x18\x01 \x01(\x0b\x32\x0f.CompLib.Header\x12\x1f\n\x06status\x18\x02 \x01(\x0b\x32\x0f.CompLib.Status\">\n\x1b\x45ncoderReadPositionsRequest\x12\x1f\n\x06header\x18\x01 \x01(\x0b\x32\x0f.CompLib.Header\"w\n\x1c\x45ncoderReadPositionsResponse\x12\x1f\n\x06header\x18\x01 \x01(\x0b\x32\x0f.CompLib.Header\x12\x1f\n\x06status\x18\x02 \x01(\x0b\x32\x0f.CompLib.Status\x12\x15\n\tpositions\x18\x03 \x03(\x05\x42\x02\x10\x01\"?\n\x1c\x45ncoderReadVelocitiesRequest\x12\x1f\n\x06header\x18\x01 \x01(\x0b\x32\x0f.CompLib.Header\"y\n\x1d\x45ncoderReadVelocitiesResponse\x12\x1f\n\x06header\x18\x01 \x01(\x0b\x32\x0f.CompLib.Header\x12\x1f\n\x06status\x18\x02 \x01(\x0b\x32\x0f.CompLib.Status\x12\x16\n\nvelocities\x18\x03 \x03(\x01\x42\x02\x10\x01\"9\n\x16IRSensorsEnableRequest\x12\x1f\n\x06header\x18\x01 \x01(\x0b\x32\x0f.CompLib.Header\":\n\x17IRSensorsDisableRequest\x12\x1f\n\x06header\x18\x01 \x01(\x0b\x32\x0f.CompLib.Header\":\n\x17IRSensorsReadAllRequest\x12\x1f\n\x06header\x18\x01 \x01(\x0b\x32\x0f.CompLib.Header\"n\n\x18IRSensorsReadAllResponse\x12\x1f\n\x06header\x18\x01 \x01(\x0b\x32\x0f.CompLib.Header\x12\x1f\n\x06status\x18\x02 \x01(\x0b\x32\x0f.CompLib.Status\x12\x10\n\x04\x64\x61ta\x18\x03 \x03(\rB\x02\x10\x01\"U\n\x15MotorsSetPowerRequest\x12\x1f\n\x06header\x18\x01 \x01(\x0b\x32\x0f.CompLib.Header\x12\x0c\n\x04port\x18\x02 \x01(\r\x12\r\n\x05power\x18\x03 \x01(\x01\"U\n\x15MotorsSetSpeedRequest\x12\x1f\n\x06header\x18\x01 \x01(\x0b\x32\x0f.CompLib.Header\x12\x0c\n\x04port\x18\x02 \x01(\r\x12\r\n\x05speed\x18\x03 \x01(\x01\"6\n\x13OdometryReadRequest\x12\x1f\n\x06header\x18\x01 \x01(\x0b\x32\x0f.CompLib.Header\"\x95\x01\n\x14OdometryReadResponse\x12\x1f\n\x06header\x18\x01 \x01(\x0b\x32\x0f.CompLib.Header\x12\x1f\n\x06status\x18\x02 \x01(\x0b\x32\x0f.CompLib.Status\x12\x12\n\nx_position\x18\x03 \x01(\x01\x12\x12\n\ny_position\x18\x04 \x01(\x01\x12\x13\n\x0borientation\x18\x05 \x01(\x01\"a\n\x14\x44riveDistanceRequest\x12\x1f\n\x06header\x18\x01 \x01(\x0b\x32\x0f.CompLib.Header\x12\x12\n\ndistance_m\x18\x02 \x01(\x01\x12\x14\n\x0cvelocity_m_s\x18\x03 \x01(\x01\"d\n\x12TurnDegreesRequest\x12\x1f\n\x06header\x18\x01 \x01(\x0b\x32\x0f.CompLib.Header\x12\x15\n\rangle_degrees\x18\x02 \x01(\x01\x12\x16\n\x0evelocity_rad_s\x18\x03 \x01(\x01\"l\n\x0c\x44riveRequest\x12\x1f\n\x06header\x18\x01 \x01(\x0b\x32\x0f.CompLib.Header\x12\x1b\n\x13linear_velocity_m_s\x18\x02 \x01(\x01\x12\x1e\n\x16\x61ngular_velocity_rad_s\x18\x03 \x01(\x01\x62\x06proto3')
|
||||
|
||||
|
||||
|
||||
_HEADER = DESCRIPTOR.message_types_by_name['Header']
|
||||
_STATUS = DESCRIPTOR.message_types_by_name['Status']
|
||||
_GENERICREQUEST = DESCRIPTOR.message_types_by_name['GenericRequest']
|
||||
_GENERICRESPONSE = DESCRIPTOR.message_types_by_name['GenericResponse']
|
||||
_ENCODERREADPOSITIONSREQUEST = DESCRIPTOR.message_types_by_name['EncoderReadPositionsRequest']
|
||||
_ENCODERREADPOSITIONSRESPONSE = DESCRIPTOR.message_types_by_name['EncoderReadPositionsResponse']
|
||||
_ENCODERREADVELOCITIESREQUEST = DESCRIPTOR.message_types_by_name['EncoderReadVelocitiesRequest']
|
||||
_ENCODERREADVELOCITIESRESPONSE = DESCRIPTOR.message_types_by_name['EncoderReadVelocitiesResponse']
|
||||
_IRSENSORSENABLEREQUEST = DESCRIPTOR.message_types_by_name['IRSensorsEnableRequest']
|
||||
_IRSENSORSDISABLEREQUEST = DESCRIPTOR.message_types_by_name['IRSensorsDisableRequest']
|
||||
_IRSENSORSREADALLREQUEST = DESCRIPTOR.message_types_by_name['IRSensorsReadAllRequest']
|
||||
_IRSENSORSREADALLRESPONSE = DESCRIPTOR.message_types_by_name['IRSensorsReadAllResponse']
|
||||
_MOTORSSETPOWERREQUEST = DESCRIPTOR.message_types_by_name['MotorsSetPowerRequest']
|
||||
_MOTORSSETSPEEDREQUEST = DESCRIPTOR.message_types_by_name['MotorsSetSpeedRequest']
|
||||
_ODOMETRYREADREQUEST = DESCRIPTOR.message_types_by_name['OdometryReadRequest']
|
||||
_ODOMETRYREADRESPONSE = DESCRIPTOR.message_types_by_name['OdometryReadResponse']
|
||||
_DRIVEDISTANCEREQUEST = DESCRIPTOR.message_types_by_name['DriveDistanceRequest']
|
||||
_TURNDEGREESREQUEST = DESCRIPTOR.message_types_by_name['TurnDegreesRequest']
|
||||
_DRIVEREQUEST = DESCRIPTOR.message_types_by_name['DriveRequest']
|
||||
Header = _reflection.GeneratedProtocolMessageType('Header', (_message.Message,), {
|
||||
'DESCRIPTOR' : _HEADER,
|
||||
'__module__' : 'CompLib_pb2'
|
||||
# @@protoc_insertion_point(class_scope:CompLib.Header)
|
||||
})
|
||||
_sym_db.RegisterMessage(Header)
|
||||
|
||||
Status = _reflection.GeneratedProtocolMessageType('Status', (_message.Message,), {
|
||||
'DESCRIPTOR' : _STATUS,
|
||||
'__module__' : 'CompLib_pb2'
|
||||
# @@protoc_insertion_point(class_scope:CompLib.Status)
|
||||
})
|
||||
_sym_db.RegisterMessage(Status)
|
||||
|
||||
GenericRequest = _reflection.GeneratedProtocolMessageType('GenericRequest', (_message.Message,), {
|
||||
'DESCRIPTOR' : _GENERICREQUEST,
|
||||
'__module__' : 'CompLib_pb2'
|
||||
# @@protoc_insertion_point(class_scope:CompLib.GenericRequest)
|
||||
})
|
||||
_sym_db.RegisterMessage(GenericRequest)
|
||||
|
||||
GenericResponse = _reflection.GeneratedProtocolMessageType('GenericResponse', (_message.Message,), {
|
||||
'DESCRIPTOR' : _GENERICRESPONSE,
|
||||
'__module__' : 'CompLib_pb2'
|
||||
# @@protoc_insertion_point(class_scope:CompLib.GenericResponse)
|
||||
})
|
||||
_sym_db.RegisterMessage(GenericResponse)
|
||||
|
||||
EncoderReadPositionsRequest = _reflection.GeneratedProtocolMessageType('EncoderReadPositionsRequest', (_message.Message,), {
|
||||
'DESCRIPTOR' : _ENCODERREADPOSITIONSREQUEST,
|
||||
'__module__' : 'CompLib_pb2'
|
||||
# @@protoc_insertion_point(class_scope:CompLib.EncoderReadPositionsRequest)
|
||||
})
|
||||
_sym_db.RegisterMessage(EncoderReadPositionsRequest)
|
||||
|
||||
EncoderReadPositionsResponse = _reflection.GeneratedProtocolMessageType('EncoderReadPositionsResponse', (_message.Message,), {
|
||||
'DESCRIPTOR' : _ENCODERREADPOSITIONSRESPONSE,
|
||||
'__module__' : 'CompLib_pb2'
|
||||
# @@protoc_insertion_point(class_scope:CompLib.EncoderReadPositionsResponse)
|
||||
})
|
||||
_sym_db.RegisterMessage(EncoderReadPositionsResponse)
|
||||
|
||||
EncoderReadVelocitiesRequest = _reflection.GeneratedProtocolMessageType('EncoderReadVelocitiesRequest', (_message.Message,), {
|
||||
'DESCRIPTOR' : _ENCODERREADVELOCITIESREQUEST,
|
||||
'__module__' : 'CompLib_pb2'
|
||||
# @@protoc_insertion_point(class_scope:CompLib.EncoderReadVelocitiesRequest)
|
||||
})
|
||||
_sym_db.RegisterMessage(EncoderReadVelocitiesRequest)
|
||||
|
||||
EncoderReadVelocitiesResponse = _reflection.GeneratedProtocolMessageType('EncoderReadVelocitiesResponse', (_message.Message,), {
|
||||
'DESCRIPTOR' : _ENCODERREADVELOCITIESRESPONSE,
|
||||
'__module__' : 'CompLib_pb2'
|
||||
# @@protoc_insertion_point(class_scope:CompLib.EncoderReadVelocitiesResponse)
|
||||
})
|
||||
_sym_db.RegisterMessage(EncoderReadVelocitiesResponse)
|
||||
|
||||
IRSensorsEnableRequest = _reflection.GeneratedProtocolMessageType('IRSensorsEnableRequest', (_message.Message,), {
|
||||
'DESCRIPTOR' : _IRSENSORSENABLEREQUEST,
|
||||
'__module__' : 'CompLib_pb2'
|
||||
# @@protoc_insertion_point(class_scope:CompLib.IRSensorsEnableRequest)
|
||||
})
|
||||
_sym_db.RegisterMessage(IRSensorsEnableRequest)
|
||||
|
||||
IRSensorsDisableRequest = _reflection.GeneratedProtocolMessageType('IRSensorsDisableRequest', (_message.Message,), {
|
||||
'DESCRIPTOR' : _IRSENSORSDISABLEREQUEST,
|
||||
'__module__' : 'CompLib_pb2'
|
||||
# @@protoc_insertion_point(class_scope:CompLib.IRSensorsDisableRequest)
|
||||
})
|
||||
_sym_db.RegisterMessage(IRSensorsDisableRequest)
|
||||
|
||||
IRSensorsReadAllRequest = _reflection.GeneratedProtocolMessageType('IRSensorsReadAllRequest', (_message.Message,), {
|
||||
'DESCRIPTOR' : _IRSENSORSREADALLREQUEST,
|
||||
'__module__' : 'CompLib_pb2'
|
||||
# @@protoc_insertion_point(class_scope:CompLib.IRSensorsReadAllRequest)
|
||||
})
|
||||
_sym_db.RegisterMessage(IRSensorsReadAllRequest)
|
||||
|
||||
IRSensorsReadAllResponse = _reflection.GeneratedProtocolMessageType('IRSensorsReadAllResponse', (_message.Message,), {
|
||||
'DESCRIPTOR' : _IRSENSORSREADALLRESPONSE,
|
||||
'__module__' : 'CompLib_pb2'
|
||||
# @@protoc_insertion_point(class_scope:CompLib.IRSensorsReadAllResponse)
|
||||
})
|
||||
_sym_db.RegisterMessage(IRSensorsReadAllResponse)
|
||||
|
||||
MotorsSetPowerRequest = _reflection.GeneratedProtocolMessageType('MotorsSetPowerRequest', (_message.Message,), {
|
||||
'DESCRIPTOR' : _MOTORSSETPOWERREQUEST,
|
||||
'__module__' : 'CompLib_pb2'
|
||||
# @@protoc_insertion_point(class_scope:CompLib.MotorsSetPowerRequest)
|
||||
})
|
||||
_sym_db.RegisterMessage(MotorsSetPowerRequest)
|
||||
|
||||
MotorsSetSpeedRequest = _reflection.GeneratedProtocolMessageType('MotorsSetSpeedRequest', (_message.Message,), {
|
||||
'DESCRIPTOR' : _MOTORSSETSPEEDREQUEST,
|
||||
'__module__' : 'CompLib_pb2'
|
||||
# @@protoc_insertion_point(class_scope:CompLib.MotorsSetSpeedRequest)
|
||||
})
|
||||
_sym_db.RegisterMessage(MotorsSetSpeedRequest)
|
||||
|
||||
OdometryReadRequest = _reflection.GeneratedProtocolMessageType('OdometryReadRequest', (_message.Message,), {
|
||||
'DESCRIPTOR' : _ODOMETRYREADREQUEST,
|
||||
'__module__' : 'CompLib_pb2'
|
||||
# @@protoc_insertion_point(class_scope:CompLib.OdometryReadRequest)
|
||||
})
|
||||
_sym_db.RegisterMessage(OdometryReadRequest)
|
||||
|
||||
OdometryReadResponse = _reflection.GeneratedProtocolMessageType('OdometryReadResponse', (_message.Message,), {
|
||||
'DESCRIPTOR' : _ODOMETRYREADRESPONSE,
|
||||
'__module__' : 'CompLib_pb2'
|
||||
# @@protoc_insertion_point(class_scope:CompLib.OdometryReadResponse)
|
||||
})
|
||||
_sym_db.RegisterMessage(OdometryReadResponse)
|
||||
|
||||
DriveDistanceRequest = _reflection.GeneratedProtocolMessageType('DriveDistanceRequest', (_message.Message,), {
|
||||
'DESCRIPTOR' : _DRIVEDISTANCEREQUEST,
|
||||
'__module__' : 'CompLib_pb2'
|
||||
# @@protoc_insertion_point(class_scope:CompLib.DriveDistanceRequest)
|
||||
})
|
||||
_sym_db.RegisterMessage(DriveDistanceRequest)
|
||||
|
||||
TurnDegreesRequest = _reflection.GeneratedProtocolMessageType('TurnDegreesRequest', (_message.Message,), {
|
||||
'DESCRIPTOR' : _TURNDEGREESREQUEST,
|
||||
'__module__' : 'CompLib_pb2'
|
||||
# @@protoc_insertion_point(class_scope:CompLib.TurnDegreesRequest)
|
||||
})
|
||||
_sym_db.RegisterMessage(TurnDegreesRequest)
|
||||
|
||||
DriveRequest = _reflection.GeneratedProtocolMessageType('DriveRequest', (_message.Message,), {
|
||||
'DESCRIPTOR' : _DRIVEREQUEST,
|
||||
'__module__' : 'CompLib_pb2'
|
||||
# @@protoc_insertion_point(class_scope:CompLib.DriveRequest)
|
||||
})
|
||||
_sym_db.RegisterMessage(DriveRequest)
|
||||
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
|
||||
DESCRIPTOR._options = None
|
||||
_ENCODERREADPOSITIONSRESPONSE.fields_by_name['positions']._options = None
|
||||
_ENCODERREADPOSITIONSRESPONSE.fields_by_name['positions']._serialized_options = b'\020\001'
|
||||
_ENCODERREADVELOCITIESRESPONSE.fields_by_name['velocities']._options = None
|
||||
_ENCODERREADVELOCITIESRESPONSE.fields_by_name['velocities']._serialized_options = b'\020\001'
|
||||
_IRSENSORSREADALLRESPONSE.fields_by_name['data']._options = None
|
||||
_IRSENSORSREADALLRESPONSE.fields_by_name['data']._serialized_options = b'\020\001'
|
||||
_HEADER._serialized_start=26
|
||||
_HEADER._serialized_end=56
|
||||
_STATUS._serialized_start=58
|
||||
_STATUS._serialized_end=109
|
||||
_GENERICREQUEST._serialized_start=111
|
||||
_GENERICREQUEST._serialized_end=160
|
||||
_GENERICRESPONSE._serialized_start=162
|
||||
_GENERICRESPONSE._serialized_end=245
|
||||
_ENCODERREADPOSITIONSREQUEST._serialized_start=247
|
||||
_ENCODERREADPOSITIONSREQUEST._serialized_end=309
|
||||
_ENCODERREADPOSITIONSRESPONSE._serialized_start=311
|
||||
_ENCODERREADPOSITIONSRESPONSE._serialized_end=430
|
||||
_ENCODERREADVELOCITIESREQUEST._serialized_start=432
|
||||
_ENCODERREADVELOCITIESREQUEST._serialized_end=495
|
||||
_ENCODERREADVELOCITIESRESPONSE._serialized_start=497
|
||||
_ENCODERREADVELOCITIESRESPONSE._serialized_end=618
|
||||
_IRSENSORSENABLEREQUEST._serialized_start=620
|
||||
_IRSENSORSENABLEREQUEST._serialized_end=677
|
||||
_IRSENSORSDISABLEREQUEST._serialized_start=679
|
||||
_IRSENSORSDISABLEREQUEST._serialized_end=737
|
||||
_IRSENSORSREADALLREQUEST._serialized_start=739
|
||||
_IRSENSORSREADALLREQUEST._serialized_end=797
|
||||
_IRSENSORSREADALLRESPONSE._serialized_start=799
|
||||
_IRSENSORSREADALLRESPONSE._serialized_end=909
|
||||
_MOTORSSETPOWERREQUEST._serialized_start=911
|
||||
_MOTORSSETPOWERREQUEST._serialized_end=996
|
||||
_MOTORSSETSPEEDREQUEST._serialized_start=998
|
||||
_MOTORSSETSPEEDREQUEST._serialized_end=1083
|
||||
_ODOMETRYREADREQUEST._serialized_start=1085
|
||||
_ODOMETRYREADREQUEST._serialized_end=1139
|
||||
_ODOMETRYREADRESPONSE._serialized_start=1142
|
||||
_ODOMETRYREADRESPONSE._serialized_end=1291
|
||||
_DRIVEDISTANCEREQUEST._serialized_start=1293
|
||||
_DRIVEDISTANCEREQUEST._serialized_end=1390
|
||||
_TURNDEGREESREQUEST._serialized_start=1392
|
||||
_TURNDEGREESREQUEST._serialized_end=1492
|
||||
_DRIVEREQUEST._serialized_start=1494
|
||||
_DRIVEREQUEST._serialized_end=1602
|
||||
# @@protoc_insertion_point(module_scope)
|
|
@ -1,69 +0,0 @@
|
|||
import compLib.CompLib_pb2 as CompLib_pb2
|
||||
from compLib.CompLibClient import CompLibClient
|
||||
|
||||
MOTOR_COUNT = 4
|
||||
|
||||
|
||||
class Motor(object):
|
||||
"""Class used to control the motors
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def power(port: int, percent: float):
|
||||
"""Set specified motor to percentage power
|
||||
|
||||
:param port: Port, which the motor is connected to. 0-3
|
||||
:param percent: Percentage of max speed. between -100 and 100
|
||||
:raises: IndexError
|
||||
"""
|
||||
|
||||
if port < 0 or port >= MOTOR_COUNT:
|
||||
raise IndexError("Invalid Motor port specified!")
|
||||
|
||||
if percent < -100 or percent > 100:
|
||||
raise IndexError("Invalid Motor speed specified! Speed is between -100 and 100 percent!")
|
||||
|
||||
request = CompLib_pb2.MotorsSetPowerRequest()
|
||||
request.header.message_type = request.DESCRIPTOR.full_name
|
||||
request.port = port
|
||||
request.power = percent
|
||||
|
||||
CompLibClient.send(request.SerializeToString(), request.ByteSize())
|
||||
|
||||
@staticmethod
|
||||
def speed(port: int, speed: float):
|
||||
"""Set specified motor to percentage power
|
||||
|
||||
:param port: Port, which the motor is connected to. 0-3
|
||||
:param speed: Speed at which a motor should turn in RPM
|
||||
:raises: IndexError
|
||||
"""
|
||||
|
||||
if port < 0 or port >= MOTOR_COUNT:
|
||||
raise IndexError("Invalid Motor port specified!")
|
||||
|
||||
request = CompLib_pb2.MotorsSetSpeedRequest()
|
||||
request.header.message_type = request.DESCRIPTOR.full_name
|
||||
request.port = port
|
||||
request.speed = speed
|
||||
|
||||
CompLibClient.send(request.SerializeToString(), request.ByteSize())
|
||||
|
||||
# @staticmethod
|
||||
# def all_off():
|
||||
# """
|
||||
# Turns of all motors
|
||||
# """
|
||||
# Logging.get_logger().debug(f"Motor.all_off")
|
||||
#
|
||||
# for i in range(1, MOTOR_COUNT + 1):
|
||||
# Motor.active_break(i)
|
||||
|
||||
# @staticmethod
|
||||
# def active_break(port: int):
|
||||
# """
|
||||
# Actively break with a specific motor
|
||||
#
|
||||
# :param port: Port, which the motor is connected to. 1-4
|
||||
# """
|
||||
# Motor.pwm(port, 0, MotorMode.BREAK)
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,11 +0,0 @@
|
|||
[Unit]
|
||||
Description=Monitoring service
|
||||
[Service]
|
||||
ExecStart=/usr/bin/python3 /usr/local/lib/python3.7/dist-packages/compLib/IPService.py
|
||||
Environment="debug=False"
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
Type=notify
|
||||
[Install]
|
||||
Alias=complib
|
||||
WantedBy=default.target
|
|
@ -1,90 +0,0 @@
|
|||
SOCKET_PATH = "/tmp/compLib"
|
||||
|
||||
|
||||
# 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.read_all()
|
||||
#
|
||||
# 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)
|
||||
#
|
||||
# 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
|
||||
Movement.drive_distance(0.5, 0.5)
|
||||
Movement.turn_degrees(90, math.pi * 2)
|
||||
|
||||
Movement.drive_distance(0.5, 0.5)
|
||||
Movement.turn_degrees(90, math.pi * 2)
|
||||
|
||||
Movement.drive_distance(0.5, 0.5)
|
||||
Movement.turn_degrees(90, math.pi * 2)
|
||||
|
||||
Movement.drive_distance(0.5, 0.5)
|
||||
Movement.turn_degrees(90, math.pi * 2)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
94
client_s1/.gitignore → compLib/.gitignore
vendored
94
client_s1/.gitignore → compLib/.gitignore
vendored
|
@ -1,12 +1,38 @@
|
|||
.idea
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/python,pycharm,code
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=python,pycharm,code
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/macos,python,pycharm
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=macos,python,pycharm
|
||||
|
||||
### Code ###
|
||||
.vscode/*
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
*.code-workspace
|
||||
### macOS ###
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
### macOS Patch ###
|
||||
# iCloud generated files
|
||||
*.icloud
|
||||
|
||||
### PyCharm ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
|
@ -19,6 +45,9 @@
|
|||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# AWS User-specific
|
||||
.idea/**/aws.xml
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
|
@ -69,6 +98,9 @@ atlassian-ide-plugin.xml
|
|||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# SonarLint plugin
|
||||
.idea/sonarlint/
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
|
@ -111,6 +143,10 @@ fabric.properties
|
|||
# https://plugins.jetbrains.com/plugin/12206-codestream
|
||||
.idea/codestream.xml
|
||||
|
||||
# Azure Toolkit for IntelliJ plugin
|
||||
# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
|
||||
.idea/**/azureSettings.xml
|
||||
|
||||
### Python ###
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
|
@ -128,11 +164,12 @@ dist/
|
|||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
|
@ -162,7 +199,7 @@ coverage.xml
|
|||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
pytestdebug.log
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
|
@ -183,9 +220,9 @@ instance/
|
|||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
doc/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
|
@ -196,7 +233,9 @@ profile_default/
|
|||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
|
@ -205,7 +244,22 @@ ipython_config.py
|
|||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
|
@ -223,7 +277,6 @@ venv/
|
|||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
pythonenv*
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
|
@ -246,7 +299,14 @@ dmypy.json
|
|||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# profiling data
|
||||
.prof
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/python,pycharm,code
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/macos,python,pycharm
|
|
@ -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
55
compLib/CMakeLists.txt
Normal 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
231
compLib/Camera.py
Normal 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)
|
|
@ -58,18 +58,39 @@ message IRSensorsReadAllResponse {
|
|||
repeated uint32 data = 3 [packed = true];
|
||||
}
|
||||
|
||||
message MotorsSetPowerRequest {
|
||||
message MotorSetPowerRequest {
|
||||
Header header = 1;
|
||||
uint32 port = 2;
|
||||
double power = 3;
|
||||
}
|
||||
|
||||
message MotorsSetSpeedRequest {
|
||||
message MotorsSetPowerRequest {
|
||||
Header header = 1;
|
||||
repeated MotorSetPowerRequest requests = 2;
|
||||
}
|
||||
|
||||
message MotorSetSpeedRequest {
|
||||
Header header = 1;
|
||||
uint32 port = 2;
|
||||
double speed = 3;
|
||||
}
|
||||
|
||||
message MotorsSetSpeedRequest {
|
||||
Header header = 1;
|
||||
repeated MotorSetSpeedRequest requests = 2;
|
||||
}
|
||||
|
||||
message MotorSetPulseWidthRequest {
|
||||
Header header = 1;
|
||||
uint32 port = 2;
|
||||
double percent = 3;
|
||||
}
|
||||
|
||||
message MotorsSetPulseWidthRequest {
|
||||
Header header = 1;
|
||||
repeated MotorSetPulseWidthRequest requests = 2;
|
||||
}
|
||||
|
||||
message OdometryReadRequest {
|
||||
Header header = 1;
|
||||
}
|
||||
|
@ -98,4 +119,8 @@ message DriveRequest {
|
|||
Header header = 1;
|
||||
double linear_velocity_m_s = 2;
|
||||
double angular_velocity_rad_s = 3;
|
||||
}
|
||||
|
||||
message HealthUpdateRequest {
|
||||
Header header = 1;
|
||||
}
|
70
compLib/CompLibClient.py
Normal file
70
compLib/CompLibClient.py
Normal file
|
@ -0,0 +1,70 @@
|
|||
import socket
|
||||
from threading import Lock
|
||||
|
||||
import compLib.CompLib_pb2 as CompLib_pb2
|
||||
|
||||
|
||||
class CompLibClient(object):
|
||||
UNIX_SOCKET_PATH = "/tmp/compLib"
|
||||
TCP_SOCKET_HOST = ""
|
||||
TCP_SOCKET_PORT = 9090
|
||||
SOCKET = None
|
||||
LOCK = Lock()
|
||||
|
||||
@staticmethod
|
||||
def use_unix_socket(socket_path="/tmp/compLib"):
|
||||
CompLibClient.UNIX_SOCKET_PATH = socket_path
|
||||
|
||||
CompLibClient.SOCKET = socket.socket(
|
||||
socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
CompLibClient.SOCKET.connect(CompLibClient.UNIX_SOCKET_PATH)
|
||||
|
||||
from compLib.HealthCheck import HealthUpdater
|
||||
HealthUpdater.start()
|
||||
|
||||
@staticmethod
|
||||
def use_tcp_socket(socket_host, socket_port=TCP_SOCKET_PORT):
|
||||
CompLibClient.TCP_SOCKET_HOST = socket_host
|
||||
CompLibClient.TCP_SOCKET_PORT = socket_port
|
||||
|
||||
CompLibClient.SOCKET = socket.socket(
|
||||
socket.AF_INET, socket.SOCK_STREAM)
|
||||
CompLibClient.SOCKET.connect(
|
||||
(CompLibClient.TCP_SOCKET_HOST, CompLibClient.TCP_SOCKET_PORT))
|
||||
|
||||
from compLib.HealthCheck import HealthUpdater
|
||||
HealthUpdater.start()
|
||||
|
||||
@staticmethod
|
||||
def send(data: bytes, size: int) -> bytes:
|
||||
with CompLibClient.LOCK:
|
||||
if CompLibClient.SOCKET is None:
|
||||
CompLibClient.use_unix_socket()
|
||||
|
||||
CompLibClient.SOCKET.sendall(size.to_bytes(1, byteorder='big'))
|
||||
CompLibClient.SOCKET.sendall(data)
|
||||
|
||||
response_size_bytes = CompLibClient.SOCKET.recv(1)
|
||||
response_size = int.from_bytes(
|
||||
response_size_bytes, byteorder="big")
|
||||
# print(response_size)
|
||||
|
||||
response_bytes = CompLibClient.SOCKET.recv(response_size)
|
||||
# print(response_bytes.hex())
|
||||
# print(len(response_bytes))
|
||||
|
||||
CompLibClient.check_response(response_bytes)
|
||||
|
||||
return response_bytes
|
||||
|
||||
@staticmethod
|
||||
def check_response(response_bytes: bytes) -> bool:
|
||||
# print(f"{response_bytes}")
|
||||
res = CompLib_pb2.GenericResponse()
|
||||
res.ParseFromString(response_bytes)
|
||||
|
||||
if res.status.successful:
|
||||
return True
|
||||
|
||||
# TODO: Log error message if unsuccessful
|
||||
return False
|
1212
compLib/CompLib_pb2.py
Normal file
1212
compLib/CompLib_pb2.py
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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)
|
|
@ -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)
|
23
compLib/HealthCheck.py
Normal file
23
compLib/HealthCheck.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
import threading
|
||||
import time
|
||||
|
||||
import compLib.CompLib_pb2 as CompLib_pb2
|
||||
from compLib.CompLibClient import CompLibClient
|
||||
|
||||
|
||||
class HealthUpdater(object):
|
||||
started = False
|
||||
|
||||
@staticmethod
|
||||
def start():
|
||||
if not HealthUpdater.started:
|
||||
threading.Thread(target=HealthUpdater.loop, daemon=True).start()
|
||||
HealthUpdater.started = True
|
||||
|
||||
@staticmethod
|
||||
def loop():
|
||||
while True:
|
||||
request = CompLib_pb2.HealthUpdateRequest()
|
||||
request.header.message_type = request.DESCRIPTOR.full_name
|
||||
CompLibClient.send(request.SerializeToString(), request.ByteSize())
|
||||
time.sleep(0.25)
|
|
@ -1,39 +1,45 @@
|
|||
import time
|
||||
|
||||
import compLib.CompLib_pb2 as CompLib_pb2
|
||||
|
||||
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: Tuple of all current ir sensors
|
||||
:return: Array aller Sensorwerte
|
||||
"""
|
||||
request = CompLib_pb2.IRSensorsReadAllRequest()
|
||||
request.header.message_type = request.DESCRIPTOR.full_name
|
||||
|
||||
response = CompLib_pb2.IRSensorsReadAllResponse()
|
||||
response.ParseFromString(CompLibClient.send(request.SerializeToString(), request.ByteSize()))
|
||||
response.ParseFromString(CompLibClient.send(
|
||||
request.SerializeToString(), request.ByteSize()))
|
||||
|
||||
return tuple(i for i in response.data)
|
||||
return [i for i in response.data]
|
||||
|
||||
@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
|
||||
|
||||
CompLibClient.send(request.SerializeToString(), request.ByteSize())
|
||||
time.sleep(0.1) # IR sensor reading is async -> Wait a bit
|
||||
|
||||
@staticmethod
|
||||
def disable():
|
||||
"""Turn off all IR emitters
|
||||
"""Deaktivieren der Infrarot-Sender
|
||||
"""
|
||||
request = CompLib_pb2.IRSensorsDisableRequest()
|
||||
request.header.message_type = request.DESCRIPTOR.full_name
|
||||
|
||||
CompLibClient.send(request.SerializeToString(), request.ByteSize())
|
||||
time.sleep(0.1) # IR sensor reading is async -> Wait a bit
|
142
compLib/Motor.py
Normal file
142
compLib/Motor.py
Normal file
|
@ -0,0 +1,142 @@
|
|||
import compLib.CompLib_pb2 as CompLib_pb2
|
||||
from compLib.CompLibClient import CompLibClient
|
||||
|
||||
MOTOR_COUNT = 4
|
||||
|
||||
|
||||
class Motor(object):
|
||||
"""Klasse zum Ansteuern der Motoren
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def power(port: int, percent: float):
|
||||
"""Motor auf eine prozentuale Leistung der Höchstgeschwindigkeit einstellen
|
||||
|
||||
:param port: Port, an welchen der Motor angesteckt ist. 0-3
|
||||
:param percent: Prozentsatz der Höchstgeschwindigkeit. zwischen -100 und 100
|
||||
:raises: IndexError
|
||||
"""
|
||||
|
||||
if port < 0 or port >= MOTOR_COUNT:
|
||||
raise IndexError("Invalid Motor port specified!")
|
||||
|
||||
if percent < -100 or percent > 100:
|
||||
raise IndexError(
|
||||
"Invalid Motor speed specified! Speed is between -100 and 100 percent!")
|
||||
|
||||
request = CompLib_pb2.MotorSetPowerRequest()
|
||||
request.header.message_type = request.DESCRIPTOR.full_name
|
||||
request.port = port
|
||||
request.power = percent
|
||||
|
||||
CompLibClient.send(request.SerializeToString(), request.ByteSize())
|
||||
|
||||
@staticmethod
|
||||
def multiple_power(*arguments: tuple[int, float]):
|
||||
"""Mehrere Motoren auf eine prozentuale Leistung der Höchstgeschwindigkeit einstellen
|
||||
|
||||
:param arguments: tuple von port, percentage
|
||||
:raises: IndexError
|
||||
"""
|
||||
request = CompLib_pb2.MotorsSetPowerRequest()
|
||||
request.header.message_type = request.DESCRIPTOR.full_name
|
||||
|
||||
for port, percent in arguments:
|
||||
if port < 0 or port >= MOTOR_COUNT:
|
||||
raise IndexError("Invalid Motor port specified!")
|
||||
|
||||
if percent < -100 or percent > 100:
|
||||
raise IndexError(
|
||||
"Invalid Motor speed specified! Speed is between -100 and 100 percent!")
|
||||
|
||||
inner_request = CompLib_pb2.MotorSetPowerRequest()
|
||||
inner_request.port = port
|
||||
inner_request.power = percent
|
||||
|
||||
request.requests.append(inner_request)
|
||||
|
||||
CompLibClient.send(request.SerializeToString(), request.ByteSize())
|
||||
|
||||
@staticmethod
|
||||
def speed(port: int, speed: float):
|
||||
"""Geschwindigkeit des Motors einstellen
|
||||
|
||||
: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
|
||||
"""
|
||||
|
||||
if port < 0 or port >= MOTOR_COUNT:
|
||||
raise IndexError("Invalid Motor port specified!")
|
||||
|
||||
request = CompLib_pb2.MotorSetSpeedRequest()
|
||||
request.header.message_type = request.DESCRIPTOR.full_name
|
||||
request.port = port
|
||||
request.speed = speed
|
||||
|
||||
CompLibClient.send(request.SerializeToString(), request.ByteSize())
|
||||
|
||||
@staticmethod
|
||||
def multiple_speed(*arguments: tuple[int, float]):
|
||||
"""Geschwindigkeit mehrerer Motoren einstellen
|
||||
|
||||
:param arguments: tuple von port, Geschwindigkeit in Radianten pro Sekunde (rad/s)
|
||||
:raises: IndexError
|
||||
"""
|
||||
|
||||
request = CompLib_pb2.MotorsSetSpeedRequest()
|
||||
request.header.message_type = request.DESCRIPTOR.full_name
|
||||
|
||||
for port, speed in arguments:
|
||||
if port < 0 or port >= MOTOR_COUNT:
|
||||
raise IndexError("Invalid Motor port specified!")
|
||||
|
||||
inner_request = CompLib_pb2.MotorSetSpeedRequest()
|
||||
inner_request.port = port
|
||||
inner_request.speed = speed
|
||||
|
||||
request.requests.append(inner_request)
|
||||
|
||||
CompLibClient.send(request.SerializeToString(), request.ByteSize())
|
||||
|
||||
@staticmethod
|
||||
def pulse_width(port: int, percent: float):
|
||||
"""Setzen den Pulsbreite eines Motors in Prozent der Periode
|
||||
|
||||
:param port: Port, an welchen der Motor angesteckt ist. 0-3
|
||||
:param percent: Prozent der Periode zwischen -100 und 100
|
||||
:raises: IndexError
|
||||
"""
|
||||
|
||||
if port < 0 or port >= MOTOR_COUNT:
|
||||
raise IndexError("Invalid Motor port specified!")
|
||||
|
||||
request = CompLib_pb2.MotorSetPulseWidthRequest()
|
||||
request.header.message_type = request.DESCRIPTOR.full_name
|
||||
request.port = port
|
||||
request.percent = percent
|
||||
|
||||
CompLibClient.send(request.SerializeToString(), request.ByteSize())
|
||||
|
||||
@staticmethod
|
||||
def multiple_pulse_width(*arguments: tuple[int, float]):
|
||||
"""Setzen den Pulsbreite mehrerer Motoren in Prozent der Periode
|
||||
|
||||
:param arguments: tuple von port, prozent
|
||||
:raises: IndexError
|
||||
"""
|
||||
|
||||
request = CompLib_pb2.MotorsSetPulseWidthRequest()
|
||||
request.header.message_type = request.DESCRIPTOR.full_name
|
||||
|
||||
for port, percent in arguments:
|
||||
if port < 0 or port >= MOTOR_COUNT:
|
||||
raise IndexError("Invalid Motor port specified!")
|
||||
|
||||
inner_request = CompLib_pb2.MotorSetPulseWidthRequest()
|
||||
inner_request.port = port
|
||||
inner_request.percent = percent
|
||||
|
||||
request.requests.append(inner_request)
|
||||
|
||||
CompLibClient.send(request.SerializeToString(), request.ByteSize())
|
|
@ -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
7
compLib/__init__.py
Normal 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)
|
14
compLib/include/comp_lib_node.h
Normal file
14
compLib/include/comp_lib_node.h
Normal 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
|
24
compLib/include/controls.h
Normal file
24
compLib/include/controls.h
Normal 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
67
compLib/include/motor.h
Normal 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
|
11
compLib/include/sequence_lock.h
Normal file
11
compLib/include/sequence_lock.h
Normal 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
22
compLib/package.xml
Normal 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>
|
10
compLib/src/comp_lib_node.cpp
Normal file
10
compLib/src/comp_lib_node.cpp
Normal 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
53
compLib/src/controls.cpp
Normal 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
93
compLib/src/main.cpp
Normal 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
225
compLib/src/motor.cpp
Normal 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();
|
||||
}
|
3
docs/.gitignore
vendored
Normal file
3
docs/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
build
|
||||
logs.db
|
||||
!lib
|
|
@ -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'
|
||||
|
@ -58,3 +59,11 @@ html_theme = 'sphinx_rtd_theme'
|
|||
# relative to this directory. They are copied after the builtin static files,
|
||||
# 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
32
docs/source/faq.rst
Normal 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/>`_
|
||||
|
||||
|
11
docs/source/gettingStarted/codeServer.rst
Normal file
11
docs/source/gettingStarted/codeServer.rst
Normal file
|
@ -0,0 +1,11 @@
|
|||
.. _gettingstarted_codeserver:
|
||||
|
||||
Programmierumgebung
|
||||
###################
|
||||
|
||||
Als Umgebung zur Programmierung des Roboters wird `code-server <https://github.com/coder/code-server>`_ eingesetzt, welche bereits am Roboter vorinstalliert ist.
|
||||
|
||||
Verbindung zur Entwicklungsumgebung herstellen
|
||||
----------------------------------------------
|
||||
Am Roboter wird die IP-Adresse des Raspberry Pi angezeigt. Um nun die Verbindung herzustellen, muss man in einem Web-Browser einfach ``<roboter_ip>:8080`` eingeben.
|
||||
Das Passwort für Visual Studio Code im Browser ist ``compair``!
|
26
docs/source/gettingStarted/firstProgram.rst
Normal file
26
docs/source/gettingStarted/firstProgram.rst
Normal file
|
@ -0,0 +1,26 @@
|
|||
Mein erstes Programm
|
||||
####################
|
||||
|
||||
Um mit der Programmierung zu beginnen, müssen wir zunächst einen neuen Ordner erstellen, in dem alle unsere Python-Dateien gespeichert werden.
|
||||
|codeServerFolder|
|
||||
|
||||
Sie können diesen Ordner nennen, wie Sie wollen, für dieses Beispiel heißt er ``compAIR``.
|
||||
Im nächsten Schritt erstellen wir unsere Datei ``main.py``.
|
||||
|codeServerFile|
|
||||
|
||||
Dann können wir beginnen, unseren Code in diese Datei zu schreiben.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
print("Hallo Welt")
|
||||
|
||||
Praktischerweise können wir die Datei auch über die VS-Code-Plattform ausführen.
|
||||
|codeServerRun|
|
||||
|
||||
Dann öffnet sich ein Terminal, der die Ausgabe unseres Programms anzeigt.
|
||||
|codeServerTerminal|
|
||||
|
||||
.. |codeServerFolder| image:: images/06_codeServerFolder.png
|
||||
.. |codeServerFile| image:: images/03_codeServerFile.png
|
||||
.. |codeServerRun| image:: images/04_codeServerRun.png
|
||||
.. |codeServerTerminal| image:: images/05_codeServerTerminal.png
|
BIN
docs/source/gettingStarted/images/01_boot.png
Normal file
BIN
docs/source/gettingStarted/images/01_boot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 112 KiB |
BIN
docs/source/gettingStarted/images/02_psk.png
Normal file
BIN
docs/source/gettingStarted/images/02_psk.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 239 KiB |
BIN
docs/source/gettingStarted/images/03_codeServerFile.png
Normal file
BIN
docs/source/gettingStarted/images/03_codeServerFile.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue