updated template for 2nd round of RoboLab Spring 2018

This commit is contained in:
7HAL32 2017-03-05 23:03:21 +01:00 committed by Lutz Thies
commit e6b628f41a
11 changed files with 629 additions and 0 deletions

198
.gitignore vendored Normal file
View File

@ -0,0 +1,198 @@
# Created by https://www.gitignore.io/api/pycharm,python,windows,macos,linux
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### macOS ###
*.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### PyCharm ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff:
.idea
# Mongo Explorer plugin:
.idea/**/mongoSettings.xml
## File-based project format:
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
### PyCharm Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
# *.iml
# modules.xml
# .idea/misc.xml
# *.ipr
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# 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/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# dotenv
.env
# virtualenv
.venv
venv/
ENV/
# Spyder project settings
.spyderproject
# Rope project settings
.ropeproject
### Windows ###
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
# End of https://www.gitignore.io/api/pycharm,python,windows,macos,linux

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "robolab-deploy"]
path = robolab-deploy
url = https://github.com/7HAL32/robolab-deploy

25
LICENSE Normal file
View File

@ -0,0 +1,25 @@
The MIT License (MIT)
Copyright (c) 2017-2018 Lutz Thies
Other contributors: Frank Busse, Felix Döring, Paul Genssler, Felix Wittwer,
Max Friedrich, Ian List, Kilian Koeltzsch and
Sinthujan Thanabalasingam
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

152
README.md Normal file
View File

@ -0,0 +1,152 @@
# RoboLab Template
Template for the RoboLab courses in spring and autumn which are conducted by the Systems Engineering Group at the Department of Computer Science, TU Dresden. Acts as a base repository that groups clone and then set the upstream to their assigned repo afterwards. Provides scripts to speed up and automate the process of deploying as well as executing Python code on LEGO MINDSTORMS EV3 robots running the customized, Debian based operating system [ev3dev-robolab](https://github.com/7HAL32/ev3dev-robolab). Includes the programming interface which is used to check parts of the students solutions in the final exam.
## Workflow
Contains a deploy script that syncs Python files from the local [src/](/src/) folder to a remote EV3 brick. This functionality is made available by the submodule [robolab-deploy](https://github.com/7HAL32/robolab-deploy). Its contents were not directly included in this repository, as the submodule allows easier updating without manually adding files to a groups repository. Afterwards this scripts attaches to a pre-loaded tmux session on the remote device, which is running `python3.6` including some modules that are already imported. These, for instance the [Python language bindings for the EV3](https://github.com/rhempel/ev3dev-lang-python), usually take way to long for practical development and debugging. The script further performs a reload on the [`main.py`](/src/main.py) file in the remote `/home/robot/src/` folder and starts execution from `main.run()`. This is made possible by the custom systemd service [ev3-robolab-startup](https://github.com/7HAL32/ev3-robolab-startup) that runs automatically on our OS after boot. Also comes with a simple example `main.py` file that prints `Hello World!` and the programming interface for the corresponding task. After the exam parts of the solutions of all group repositories will be checked and tested for correctness.
The most recent version regarding the current RoboLab can be found in the default branch, which is named and set according to this course. A clean "version" of the template is always available from the `master` branch.
## Installation
These steps should be only performed by **one** member of your group.
1. Clone the repository to any local destination.
```
git clone --recursive https://github.com/7HAL32/robolab-template
```
The flag `--recursive` initializes the submodule `robolab-deploy`.
2. Change to the working directory.
```
cd ./robolab-template
```
3. Set the remote upstream to your group repository.
```
git remote set-url origin https://bitbucket.org/robolab-<season>-<year>/group-<id>
```
`<season>` is either `spring` or `autumn` depending on which RoboLab course you are participating in, i.e. Spring Course (INF) or Autumn Course (NES).
`<year>` is the year your course has started in the format `yy`. For instance if the introduction took place on March 06th, 2017 `<year>` will be `17`.
`<id>` has been assigned to you at the beginning of the course. Please make sure to include leading zeros and fill up the id to three digits, e.g. group 42 will enter `042`.
4. Verify, if the new upstream has been set successfully.
```
git remote -v
```
5. Perform an initial push.
```
git push origin master
```
6. Now the other members of your team are ready to clone your group repository. Make sure to enter the corresponding URL from step (3) and also use the `--recursive` flag.
```
git clone --recursive https://bitbucket.org/robolab-<season>-<year>/group-<id>
```
## Dependencies
In order to function you will need to have [Python 3.6](https://www.python.org/downloads/) installed. All other auxiliary files will be downloaded by the script itself. On Linux you may also need to install [sshpass](https://gist.github.com/arunoda/7790979) manually.
**Make sure that you are connected to the campus network on the first run to fetch all dependencies.**
## Usage
All source file must reside inside the `src/` sub directory. To start deploying and executing, call the file according to your operating system configuration and setup of Python.
### Linux and macOS
There is a so called [Shebang](https://en.wikipedia.org/wiki/Shebang_(Unix)) on the top of the scripts. This should automatically resolve the Python executable in most cases.
```
./deploy.py [optional arguments]
```
### Windows
Unfortunately some investigation and work on Windows may be necessary as the mentioned Shebang does not work here.
```
PYTHON_EXECUTABLE ./deploy.py [optional arguments]
```
The variable `PYTHON_EXECUTABLE` contains either the shortcut registered in your systems `$PATH` environment or the full direct path to the `python.exe`.
## What's inside?
### ./src/
Directory for all source files that will be synced to the brick.
Keep in mind, that many files slow down the process of copying, especially when relying on wireless connections to the remote EV3\. It is therefore recommended to keep only recent and relevant files in this folder.
### ./src/main.py
Central hub for the execution of any code.
The deploy script will call the function `main.run()`. Make sure that all modules are imported in this file and are called appropriately from within `run()`.
### ./deploy.py
Simple stub that calls the "real" `deploy.py` in the git submodule without an additional path prefix and passes along any parameters without modification.
This approach was chosen to allow updates in a very simple manner, without giving anyone headaches or create potential chaos due to merge conflicts and other similar entertaining problems. The process of updating is described in a section down below.
#### ./robolab-deploy/
Contains the "real" deploy module.
Including configuration files, scripts and necessary auxiliary binaries like `putty` and `pscp`. It is also the place for several other files, for instance login credentials, IP addresses and so on. These will not be added to any commit unless your override the .gitignore. For more information, please visit the [submodules repository](ttps://github.com/7HAL32/robolab-deploy).
## Updating
Hopefully updates are only necessary in order to get cool new features that have been added. Well or in case any bug was found and had to be fixed, but that happens like, you know, never. Luckily this process is fairly simple, as you make a pull in the submodule from the master and add the updated detached HEAD to a commit in the template repository.
```
git submodule foreach git pull origin master
git add robolab-deploy
git commit -m "updated submodule to lastest version of the upstream head"
git push
```
## Help
For additional information on usage, optional arguments, syntax, et cetera simply call the stub `deploy.py` with the `--help` flag. There's also an extensive section on this template, the deploy scripts and the interface in the [RoboLab Docs](http://robolab.inf.tu-dresden.de) which are accessible via the campus network of TU Dresden.
## Credits
Contributors to robolab-template:
- [Frank Busse](https://github.com/251) (interface)
- Lutz Thies (description and deploy stub)
- Max Friedrich, Ian List, Kilian Koeltzsch and Sinthujan Thanabalasingam
Contributors to [robolab-deploy](ttps://github.com/7HAL32/robolab-deploy) (submodule):
- Version of 2016
- [Felix Döring](https://github.com/h4llow3En)
- [Felix Wittwer](https://github.com/Feliix42)
- Version of 2017
- [Paul Genssler](https://github.com/krabo0om) (systemd restart, debugging and testing for windows, emotional support)
- Lutz Thies (rewrite and redesign, i.e. systemd, tmux, reloader)
Part of the RoboLab project
Systems Engineering Group, Faculty of Computer Science, TU Dresden
Copyright (c) 2017-2018 by Lutz Thies
Released under the [MIT License](/LICENSE)

36
deploy.py Executable file
View File

@ -0,0 +1,36 @@
#!/usr/bin/env python3
"""
Simple stub that calls the 'real' deploy.py in the git submodule without an
additional path prefix. Passes along any parameters without modification.
For usage, optional arguments, syntax, et cetera please refer to the README.md
of this repository, the 'robolab-deploy' submodule or the RoboLab Docs which
are accessible at http://robolab.inf.tu-dresden.de
This module: https://github.com/7HAL32/robolab-template
The submodule: https://github.com/7HAL32/robolab-deploy
Part of the RoboLab project
Systems Engineering Group, Faculty of Computer Science, TU Dresden
Copyright (c) 2017-2018 by Lutz Thies
Released under the MIT License
"""
import sys
import subprocess
# check if somebody forgot to use the --recursive flag
try:
with open("./robolab-deploy/deploy.py") as f:
pass
except FileNotFoundError:
print("You forgot to use the --recursive flag while cloning this repository.")
print("Please run: git submodule update --init --recursive")
# get the full executable path, because windows can't handle our shebang
PYTHON_EXECUTABLE = sys.executable
# it's basically a one-liner \o/
subprocess.call([PYTHON_EXECUTABLE,
'./robolab-deploy/deploy.py'] + sys.argv[1:])

1
robolab-deploy Submodule

@ -0,0 +1 @@
Subproject commit 9a8fd3c4a2e5897358abbf67775f8fdd33540cd8

29
src/communication.py Normal file
View File

@ -0,0 +1,29 @@
#!/usr/bin/env python3
# Suggestion: Do not import the ev3dev.ev3 module in this file
class Communication:
"""
Class to hold the MQTT client
Feel free to add functions, change the constructor and the example send_message() to satisfy your requirements and thereby solve the task according to the specifications
"""
def __init__(self, mqtt_client):
""" Initializes communication module, connect to server, subscribe, etc. """
# THESE TWO VARIABLES MUST NOT BE CHANGED
self.client = mqtt_client
self.client.on_message = self.on_message
# ADD YOUR VARIABLES HERE
# THIS FUNCTIONS SIGNATURE MUST NOT BE CHANGED
def on_message(self, client, data, message):
""" Handles the callback if any message arrived """
pass
# Example
def send_message(self, topic, message):
""" Sends given message to specified channel """
pass

26
src/main.py Normal file
View File

@ -0,0 +1,26 @@
#!/usr/bin/env python3
import ev3dev.ev3 as ev3
import uuid
import paho.mqtt.client as mqtt
from planet import Direction, Planet
from communication import Communication
client = None # DO NOT EDIT
def run():
# DO NOT EDIT
global client
client = mqtt.Client(client_id=str(uuid.uuid4()), # client_id has to be unique among ALL users
clean_session=False,
protocol=mqtt.MQTTv31)
# the execution of all code shall be started from within this function
# ADD YOUR OWN IMPLEMENTATION HEREAFTER
print("Hello World!")
# DO NOT EDIT
if __name__ == '__main__':
run()

2
src/odometry.py Normal file
View File

@ -0,0 +1,2 @@
# Suggestion: implement odometry as class that is not using the ev3dev.ev3 package
# establish value exchange with main driving class via getters and setters

72
src/planet.py Normal file
View File

@ -0,0 +1,72 @@
#!/usr/bin/env python3
from enum import IntEnum, unique
from typing import List, Optional, Tuple, Dict
# IMPORTANT NOTE: DO NOT IMPORT THE ev3dev.ev3 MODULE IN THIS FILE
@unique
class Direction(IntEnum):
""" Directions in degrees """
NORTH = 0
EAST = 90
SOUTH = 180
WEST = 270
# simple alias, no magic here
Weight = int
"""
Weight of a given path (received from the server)
value: -1 if broken path
>0 for all other paths
never 0
"""
class Planet:
"""
Contains the representation of the map and provides certain functions to manipulate it according to the specifications
"""
def __init__(self):
""" Initializes the data structure """
self.target = None
def add_path(self, start: Tuple[Tuple[int, int], Direction], target: Tuple[Tuple[int, int], Direction], weight: int):
"""
Adds a bidirectional path defined between the start and end coordinates to the map and assigns the weight to it
example:
add_path(((0, 3), Direction.NORTH), ((0, 3), Direction.WEST), 1)
"""
pass
def get_paths(self) -> Dict[Tuple[int, int], Dict[Direction, Tuple[Tuple[int, int], Direction, Weight]]]:
"""
Returns all paths
example:
get_paths() returns: {
(0, 3): {
Direction.NORTH: ((0, 3), Direction.WEST, 1),
Direction.EAST: ((1, 3), Direction.WEST, 2)
},
(1, 3): {
Direction.WEST: ((0, 3), Direction.EAST, 2),
...
},
...
}
"""
pass
def shortest_path(self, start: Tuple[int, int], target: Tuple[int, int]) -> Optional[List[Tuple[Tuple[int, int], Direction]]]:
"""
Returns a shortest path between two nodes
examples:
shortest_path((0,0), (2,2)) returns: [((0, 0), Direction.EAST), ((1, 0), Direction.NORTH)]
shortest_path((0,0), (1,2)) returns: None
"""
pass

85
src/planettest.py Executable file
View File

@ -0,0 +1,85 @@
#!/usr/bin/env python3
import unittest
from planet import Direction, Planet
class ExampleTestPlanet(unittest.TestCase):
def setUp(self):
"""
Instantiates the planet data structure and fills it with paths
example planet:
+--+
| |
+-0,3------+
| |
0,2-----2,2 (target)
| /
+-0,1 /
| | /
+-0,0-1,0
|
(start)
"""
# set your data structure
self.planet = Planet()
# add the paths
self.planet.add_path(((0, 0), Direction.NORTH), ((0, 1), Direction.SOUTH), 1)
self.planet.add_path(((0, 1), Direction.WEST), ((0, 0), Direction.WEST), 1)
def test_target_not_reachable_with_loop(self):
# does the shortest path algorithm loop infinitely?
# there is no shortest path
self.assertIsNone(self.planet.shortest_path((0, 0), (1, 2)))
class YourFirstTestPlanet(unittest.TestCase):
def setUp(self):
"""
Instantiates the planet data structure and fills it with paths
MODEL YOUR TEST PLANET HERE (if you'd like):
"""
# set your data structure
self.planet = Planet()
# ADD YOUR PATHS HERE:
# self.planet.add_path(...)
def test_integrity(self):
# were all paths added correctly to the planet
# check if add_path() works by using get_paths()
self.fail('implement me!')
def test_empty_planet(self):
self.fail('implement me!')
def test_target_not_reachable(self):
self.fail('implement me!')
def test_shortest_path(self):
# at least 2 possible paths
self.fail('implement me!')
def test_same_length(self):
# at least 2 possible paths with the same weight
self.fail('implement me!')
def test_shortest_path_with_loop(self):
# does the shortest path algorithm loop infinitely?
# there is a shortest path
self.fail('implement me!')
def test_target_not_reachable_with_loop(self):
# there is no shortest path
self.fail('implement me!')
if __name__ == "__main__":
unittest.main()