commit e6b628f41a90e6fe3a716163688c9651859553bf Author: 7HAL32 Date: Sun Mar 5 23:03:21 2017 +0100 updated template for 2nd round of RoboLab Spring 2018 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f1e646f --- /dev/null +++ b/.gitignore @@ -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 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..922e6cd --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "robolab-deploy"] + path = robolab-deploy + url = https://github.com/7HAL32/robolab-deploy diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4072c74 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7ba5640 --- /dev/null +++ b/README.md @@ -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--/group- + ``` + + `` is either `spring` or `autumn` depending on which RoboLab course you are participating in, i.e. Spring Course (INF) or Autumn Course (NES). + + `` is the year your course has started in the format `yy`. For instance if the introduction took place on March 06th, 2017 `` will be `17`. + + `` 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--/group- + ``` + +## 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) diff --git a/deploy.py b/deploy.py new file mode 100755 index 0000000..3911752 --- /dev/null +++ b/deploy.py @@ -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:]) diff --git a/robolab-deploy b/robolab-deploy new file mode 160000 index 0000000..9a8fd3c --- /dev/null +++ b/robolab-deploy @@ -0,0 +1 @@ +Subproject commit 9a8fd3c4a2e5897358abbf67775f8fdd33540cd8 diff --git a/src/communication.py b/src/communication.py new file mode 100644 index 0000000..d643dfa --- /dev/null +++ b/src/communication.py @@ -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 diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..a1edf3a --- /dev/null +++ b/src/main.py @@ -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() diff --git a/src/odometry.py b/src/odometry.py new file mode 100644 index 0000000..03bdd29 --- /dev/null +++ b/src/odometry.py @@ -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 \ No newline at end of file diff --git a/src/planet.py b/src/planet.py new file mode 100644 index 0000000..c601dea --- /dev/null +++ b/src/planet.py @@ -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 diff --git a/src/planettest.py b/src/planettest.py new file mode 100755 index 0000000..4582188 --- /dev/null +++ b/src/planettest.py @@ -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()