Cleanup
2
build.sh
|
@ -4,7 +4,7 @@ mkdir output
|
|||
|
||||
DEB="empty"
|
||||
|
||||
cd client_s2
|
||||
cd client
|
||||
source build_deb.sh
|
||||
echo "Ran build deb, created: $DEB"
|
||||
mv $DEB ../output
|
||||
|
|
0
client_s2/.gitignore → client/.gitignore
vendored
0
client_s2/.idea/.gitignore → client/.idea/.gitignore
generated
vendored
0
client_s2/.idea/misc.xml → client/.idea/misc.xml
generated
0
client_s2/.idea/vcs.xml → client/.idea/vcs.xml
generated
Before Width: | Height: | Size: 188 KiB After Width: | Height: | Size: 188 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
Before Width: | Height: | Size: 391 KiB After Width: | Height: | Size: 391 KiB |
Before Width: | Height: | Size: 356 KiB After Width: | Height: | Size: 356 KiB |
252
client_s1/.gitignore
vendored
|
@ -1,252 +0,0 @@
|
|||
.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
|
||||
|
||||
### Code ###
|
||||
.vscode/*
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
*.code-workspace
|
||||
|
||||
### 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
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
### Python ###
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
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/
|
||||
pytestdebug.log
|
||||
|
||||
# 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/
|
||||
doc/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.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
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
pythonenv*
|
||||
|
||||
# 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/
|
||||
|
||||
# profiling data
|
||||
.prof
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/python,pycharm,code
|
|
@ -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
|
@ -1,2 +0,0 @@
|
|||
build
|
||||
logs.db
|
|
@ -1,60 +0,0 @@
|
|||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# This file only contains a selection of the most common options. For a full
|
||||
# list see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
# -- Path setup --------------------------------------------------------------
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.abspath('../..'))
|
||||
sys.setrecursionlimit(1500)
|
||||
os.environ["EXTENSIVE_LOGGING"] = "False"
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'CompLib'
|
||||
copyright = '2022, Verein zur Förderung von Wissenschaft und Technik an Schulen (F-WuTS)'
|
||||
author = 'robo4you'
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = '0.2.3'
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx_rtd_theme'
|
||||
]
|
||||
|
||||
autodoc_mock_imports = ["smbus", "compLib.PCA9685", "RPi",
|
||||
"pigpio", "flask", "apt", "influxdb_client"]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This pattern also affects html_static_path and html_extra_path.
|
||||
exclude_patterns = []
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# 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']
|
|
@ -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,10 +0,0 @@
|
|||
.. _lib_encoder:
|
||||
|
||||
Encoder
|
||||
*******
|
||||
|
||||
Class Documentation
|
||||
====================
|
||||
|
||||
.. autoclass:: compLib.Encoder.Encoder
|
||||
:members:
|
|
@ -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()
|
|
@ -1 +0,0 @@
|
|||
include readme.md
|
|
@ -1,20 +0,0 @@
|
|||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line, and also
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = source
|
||||
BUILDDIR = build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
|
@ -1,35 +0,0 @@
|
|||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=source
|
||||
set BUILDDIR=build
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
|
||||
:end
|
||||
popd
|
Before Width: | Height: | Size: 188 KiB |
Before Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 69 KiB |
Before Width: | Height: | Size: 391 KiB |
Before Width: | Height: | Size: 356 KiB |
|
@ -1,23 +0,0 @@
|
|||
export BUILDING_DOCS=true
|
||||
cd docs || exit
|
||||
rm -rf build
|
||||
rm -rf gh-pages
|
||||
|
||||
make html
|
||||
|
||||
git clone git@github.com:F-WuTS/compLIB.git gh-pages
|
||||
|
||||
cd gh-pages || exit
|
||||
git checkout gh-pages
|
||||
|
||||
cp -r ../build/html/* .
|
||||
|
||||
git add .
|
||||
git commit -a -m "Update documentation"
|
||||
git push origin gh-pages
|
||||
|
||||
cd ..
|
||||
rm -rf gh-pages
|
||||
|
||||
cd ..
|
||||
export BUILDING_DOCS=false
|
187
server/.idea/workspace.xml
generated
|
@ -1,187 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CMakeRunConfigurationManager" shouldGenerate="true" shouldDeleteObsolete="true">
|
||||
<generated>
|
||||
<config projectName="compLib_server" targetName="compLib_server" />
|
||||
</generated>
|
||||
</component>
|
||||
<component name="CMakeSettings" AUTO_RELOAD="true">
|
||||
<configurations>
|
||||
<configuration PROFILE_NAME="Debug" CONFIG_NAME="Debug" />
|
||||
</configurations>
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="8af7440c-54ee-4d31-9382-d9c6297ebc16" name="Default Changelist" comment="">
|
||||
<change beforePath="$PROJECT_DIR$/CMakeLists.txt" beforeDir="false" afterPath="$PROJECT_DIR$/CMakeLists.txt" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeCache.txt" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/3.17.3/CMakeCCompiler.cmake" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/3.17.3/CMakeCXXCompiler.cmake" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/3.17.3/CMakeDetermineCompilerABI_C.bin" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/3.17.3/CMakeDetermineCompilerABI_CXX.bin" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/3.17.3/CMakeSystem.cmake" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/3.17.3/CompilerIdC/CMakeCCompilerId.c" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/3.17.3/CompilerIdCXX/CMakeCXXCompilerId.cpp" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/CMakeDirectoryInformation.cmake" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/CMakeError.log" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/CMakeOutput.log" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/CMakeRuleHashes.txt" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/Makefile.cmake" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/Makefile2" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/TargetDirectories.txt" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/clion-environment.txt" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/clion-log.txt" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/cmake.check_cache" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/compLib_server.dir/CXX.includecache" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/compLib_server.dir/DependInfo.cmake" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/compLib_server.dir/build.make" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/compLib_server.dir/cmake_clean.cmake" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/compLib_server.dir/depend.internal" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/compLib_server.dir/depend.make" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/compLib_server.dir/flags.make" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/compLib_server.dir/link.txt" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/compLib_server.dir/progress.make" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/progress.marks" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/server.dir/CXX.includecache" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/server.dir/DependInfo.cmake" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/server.dir/build.make" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/server.dir/cmake_clean.cmake" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/server.dir/depend.internal" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/server.dir/depend.make" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/server.dir/flags.make" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/server.dir/link.txt" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CMakeFiles/server.dir/progress.make" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CompLib.pb.cc" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/CompLib.pb.h" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/Makefile" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/cmake_install.cmake" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/compLib_server" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/compLib_server.cbp" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/server" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmake-build-debug/server.cbp" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/Spi.cpp" beforeDir="false" afterPath="$PROJECT_DIR$/src/Spi.cpp" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="ClangdSettings">
|
||||
<option name="formatViaClangd" value="false" />
|
||||
</component>
|
||||
<component name="ExecutionTargetManager" SELECTED_TARGET="CMakeBuildProfile:Debug" />
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." />
|
||||
</component>
|
||||
<component name="ProjectId" id="260hg7Cr430WVTtVvH0hiiD9T6r" />
|
||||
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="autoscrollFromSource" value="true" />
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent">
|
||||
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
|
||||
<property name="WebServerToolWindowFactoryState" value="false" />
|
||||
<property name="cf.first.check.clang-format" value="false" />
|
||||
<property name="last_opened_file_path" value="$PROJECT_DIR$/third_party/asio" />
|
||||
<property name="settings.editor.selected.configurable" value="settings.saveactions" />
|
||||
</component>
|
||||
<component name="RecentsManager">
|
||||
<key name="MoveFile.RECENT_KEYS">
|
||||
<recent name="$PROJECT_DIR$" />
|
||||
<recent name="$PROJECT_DIR$/src" />
|
||||
</key>
|
||||
<key name="CopyFile.RECENT_KEYS">
|
||||
<recent name="$PROJECT_DIR$/third_party/asio" />
|
||||
</key>
|
||||
</component>
|
||||
<component name="RunManager">
|
||||
<configuration default="true" type="CLionExternalRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" PASS_PARENT_ENVS_2="true">
|
||||
<method v="2">
|
||||
<option name="CLION.EXTERNAL.BUILD" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
<configuration name="compLib_server" type="CMakeRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="compLib_server" TARGET_NAME="compLib_server" CONFIG_NAME="Debug" RUN_TARGET_PROJECT_NAME="compLib_server" RUN_TARGET_NAME="compLib_server">
|
||||
<method v="2">
|
||||
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
<configuration default="true" type="GradleAppRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" PASS_PARENT_ENVS_2="true">
|
||||
<method v="2">
|
||||
<option name="com.jetbrains.cidr.cpp.gradle.execution.GradleNativeBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||
<component name="WindowStateProjectService">
|
||||
<state x="440" y="187" key="#New_File_Extensions" timestamp="1647623370330">
|
||||
<screen x="0" y="0" width="1680" height="1050" />
|
||||
</state>
|
||||
<state x="440" y="187" key="#New_File_Extensions/0.0.1680.1050@0.0.1680.1050" timestamp="1647623370330" />
|
||||
<state x="995" y="526" key="#com.intellij.fileTypes.FileTypeChooser" timestamp="1646566673308">
|
||||
<screen x="0" y="0" width="2560" height="1440" />
|
||||
</state>
|
||||
<state x="995" y="526" key="#com.intellij.fileTypes.FileTypeChooser/2560.25.2560.1415/-1680.415.1680.1025/0.0.2560.1440@0.0.2560.1440" timestamp="1646566673308" />
|
||||
<state width="2518" height="314" key="GridCell.Tab.0.bottom" timestamp="1646668312027">
|
||||
<screen x="0" y="0" width="2560" height="1440" />
|
||||
</state>
|
||||
<state width="1638" height="314" key="GridCell.Tab.0.bottom/0.0.1680.1050@0.0.1680.1050" timestamp="1646595844143" />
|
||||
<state width="1638" height="314" key="GridCell.Tab.0.bottom/0.25.1680.1025@0.25.1680.1025" timestamp="1646594478732" />
|
||||
<state width="1638" height="314" key="GridCell.Tab.0.bottom/2560.25.2560.1415/-1680.390.1680.1050/0.25.2560.1415@-1680.390.1680.1050" timestamp="1646576156561" />
|
||||
<state width="2518" height="314" key="GridCell.Tab.0.bottom/2560.25.2560.1415/-1680.415.1680.1025/0.0.2560.1440@0.0.2560.1440" timestamp="1646668312027" />
|
||||
<state width="2518" height="314" key="GridCell.Tab.0.center" timestamp="1646668312026">
|
||||
<screen x="0" y="0" width="2560" height="1440" />
|
||||
</state>
|
||||
<state width="1638" height="314" key="GridCell.Tab.0.center/0.0.1680.1050@0.0.1680.1050" timestamp="1646595844142" />
|
||||
<state width="1638" height="314" key="GridCell.Tab.0.center/0.25.1680.1025@0.25.1680.1025" timestamp="1646594478731" />
|
||||
<state width="1638" height="314" key="GridCell.Tab.0.center/2560.25.2560.1415/-1680.390.1680.1050/0.25.2560.1415@-1680.390.1680.1050" timestamp="1646576156560" />
|
||||
<state width="2518" height="314" key="GridCell.Tab.0.center/2560.25.2560.1415/-1680.415.1680.1025/0.0.2560.1440@0.0.2560.1440" timestamp="1646668312026" />
|
||||
<state width="2518" height="314" key="GridCell.Tab.0.left" timestamp="1646668312026">
|
||||
<screen x="0" y="0" width="2560" height="1440" />
|
||||
</state>
|
||||
<state width="1638" height="314" key="GridCell.Tab.0.left/0.0.1680.1050@0.0.1680.1050" timestamp="1646595844141" />
|
||||
<state width="1638" height="314" key="GridCell.Tab.0.left/0.25.1680.1025@0.25.1680.1025" timestamp="1646594478730" />
|
||||
<state width="1638" height="314" key="GridCell.Tab.0.left/2560.25.2560.1415/-1680.390.1680.1050/0.25.2560.1415@-1680.390.1680.1050" timestamp="1646576156559" />
|
||||
<state width="2518" height="314" key="GridCell.Tab.0.left/2560.25.2560.1415/-1680.415.1680.1025/0.0.2560.1440@0.0.2560.1440" timestamp="1646668312026" />
|
||||
<state width="2518" height="314" key="GridCell.Tab.0.right" timestamp="1646668312027">
|
||||
<screen x="0" y="0" width="2560" height="1440" />
|
||||
</state>
|
||||
<state width="1638" height="314" key="GridCell.Tab.0.right/0.0.1680.1050@0.0.1680.1050" timestamp="1646595844143" />
|
||||
<state width="1638" height="314" key="GridCell.Tab.0.right/0.25.1680.1025@0.25.1680.1025" timestamp="1646594478731" />
|
||||
<state width="1638" height="314" key="GridCell.Tab.0.right/2560.25.2560.1415/-1680.390.1680.1050/0.25.2560.1415@-1680.390.1680.1050" timestamp="1646576156560" />
|
||||
<state width="2518" height="314" key="GridCell.Tab.0.right/2560.25.2560.1415/-1680.415.1680.1025/0.0.2560.1440@0.0.2560.1440" timestamp="1646668312027" />
|
||||
<state width="1638" height="364" key="GridCell.Tab.1.bottom" timestamp="1646581971712">
|
||||
<screen x="0" y="0" width="1680" height="1050" />
|
||||
</state>
|
||||
<state width="1638" height="364" key="GridCell.Tab.1.bottom/0.0.1680.1050@0.0.1680.1050" timestamp="1646581971712" />
|
||||
<state width="1638" height="364" key="GridCell.Tab.1.bottom/2560.25.2560.1415/-1680.390.1680.1050/0.25.2560.1415@-1680.390.1680.1050" timestamp="1646574297963" />
|
||||
<state width="1638" height="364" key="GridCell.Tab.1.center" timestamp="1646581971711">
|
||||
<screen x="0" y="0" width="1680" height="1050" />
|
||||
</state>
|
||||
<state width="1638" height="364" key="GridCell.Tab.1.center/0.0.1680.1050@0.0.1680.1050" timestamp="1646581971711" />
|
||||
<state width="1638" height="364" key="GridCell.Tab.1.center/2560.25.2560.1415/-1680.390.1680.1050/0.25.2560.1415@-1680.390.1680.1050" timestamp="1646574297962" />
|
||||
<state width="1638" height="364" key="GridCell.Tab.1.left" timestamp="1646581971711">
|
||||
<screen x="0" y="0" width="1680" height="1050" />
|
||||
</state>
|
||||
<state width="1638" height="364" key="GridCell.Tab.1.left/0.0.1680.1050@0.0.1680.1050" timestamp="1646581971711" />
|
||||
<state width="1638" height="364" key="GridCell.Tab.1.left/2560.25.2560.1415/-1680.390.1680.1050/0.25.2560.1415@-1680.390.1680.1050" timestamp="1646574297962" />
|
||||
<state width="1638" height="364" key="GridCell.Tab.1.right" timestamp="1646581971711">
|
||||
<screen x="0" y="0" width="1680" height="1050" />
|
||||
</state>
|
||||
<state width="1638" height="364" key="GridCell.Tab.1.right/0.0.1680.1050@0.0.1680.1050" timestamp="1646581971711" />
|
||||
<state width="1638" height="364" key="GridCell.Tab.1.right/2560.25.2560.1415/-1680.390.1680.1050/0.25.2560.1415@-1680.390.1680.1050" timestamp="1646574297963" />
|
||||
<state x="349" y="161" key="SettingsEditor" timestamp="1646594428197">
|
||||
<screen x="0" y="0" width="1680" height="1050" />
|
||||
</state>
|
||||
<state x="349" y="161" key="SettingsEditor/0.0.1680.1050@0.0.1680.1050" timestamp="1646594428197" />
|
||||
<state width="600" height="428" key="javadoc.popup" timestamp="1646570231808">
|
||||
<screen x="0" y="0" width="2560" height="1440" />
|
||||
</state>
|
||||
<state width="600" height="428" key="javadoc.popup/2560.25.2560.1415/-1680.415.1680.1025/0.0.2560.1440@0.0.2560.1440" timestamp="1646570231808" />
|
||||
<state x="620" y="241" width="670" height="493" key="search.everywhere.popup" timestamp="1646595572573">
|
||||
<screen x="0" y="0" width="1680" height="1050" />
|
||||
</state>
|
||||
<state x="620" y="241" width="670" height="493" key="search.everywhere.popup/0.0.1680.1050@0.0.1680.1050" timestamp="1646595572573" />
|
||||
<state x="945" y="331" width="670" height="676" key="search.everywhere.popup/2560.25.2560.1415/-1680.415.1680.1025/0.0.2560.1440@0.0.2560.1440" timestamp="1646568792293" />
|
||||
</component>
|
||||
</project>
|