diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..67e2665 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*.sw? +__pycache__ +MANIFEST +dist +etc/config.yml +build +*.pyc +*.pyo diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..e1f5fd8 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,6 @@ +# file GENERATED by distutils, do NOT edit +include setup.py +include README.md +include MANIFEST.in +recursive-include pyb00st *.py + diff --git a/RPM/python3-pyb00st.spec b/RPM/python3-pyb00st.spec new file mode 100644 index 0000000..88c701c --- /dev/null +++ b/RPM/python3-pyb00st.spec @@ -0,0 +1,78 @@ +# +# spec file for package python-pyb00st +# +# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany. +# +# All modifications and additions to the file contributed by third parties +# remain the property of their copyright owners, unless otherwise agreed +# upon. The license for this file, and modifications and additions to the +# file, is the same license as for the pristine package itself (unless the +# license for the pristine package is not an Open Source License, in which +# case the license is the MIT License). An "Open Source License" is a +# license that conforms to the Open Source Definition (Version 1.9) +# published by the Open Source Initiative. + +# Please submit bugfixes or comments via http://bugs.opensuse.org/ +# + +%define pypi_name pyb00st +%define skip_python2 1 + +Name: python3-%{pypi_name} +Version: 0.0.0 +Release: 0 +Summary: Python for LEGO BOOST +License: MIT +Group: Development/Languages/Python +Url: https://github.com/JorgePe/pyb00st +Source: %{pypi_name}-%{version}.tar.gz +BuildRequires: python-rpm-macros +BuildRequires: fdupes +BuildRequires: python3 +%if 0%{?fedora} +BuildRequires: python3-devel +%define debug_package %{nil} +%endif + +BuildRoot: %{_tmppath}/%{name}-%{version}-build +Requires: python3 +Requires: python3-pygatt +Requires: python3-PyYAML + +%description +The LEGO BOOST Move Hub is a BLE (Bluetooth Low Energy) device like the LEGO WeDo 2.0 Smart Hub + and the Vengit SBrick which I already managed to control with the LEGO MINDSTORMS EV3 thanks to + [pygattlib](https://bitbucket.org/OscarAcena/pygattlib), a python library for BLE. + +I've been [reverse engineering the LEGO BOOST](https://github.com/JorgePe/BOOSTreveng) Move Hub + and since I'm now officially crazy I decided to try to write a python package. + +I started this project with pygattlib. It's a library that makes direct use of BlueZ and has been + included in pybluez. But since python3 version of pygattlib has problems with notifications I + started to use a different library, [pygatt](https://github.com/peplin/pygatt/tree/master/pygatt), + that doesn't make direct use of Bluez - instead, it makes use of a *backend*. On linux systems + this backend can be BlueZ (through system calls to BlueZ commands like hcitool and gattool) but on + other systems (and probably also on linux aswell) it uses a different backend, based on BlueGiga's + API - so a BG adapter, like BLED112, is needed. + +Why did I call it pyb00st? Well, boost is a C++ library and there are already lots of python libraries + related to it and I don't want to add the LEGO word because I don't want troubles. + +By the way... + + +%prep +%setup -q -n %{pypi_name}-%{version} + +%build +python3 setup.py build + +%install +python3 setup.py install --prefix=%{_prefix} --root=%{buildroot} + +%files +%defattr(-,root,root) +%doc README.md +%{python3_sitelib}/* + +%changelog diff --git a/etc/config.yml.template b/etc/config.yml.template new file mode 100644 index 0000000..926c243 --- /dev/null +++ b/etc/config.yml.template @@ -0,0 +1,3 @@ +MY_MOVEHUB_ADD: 00:16:53:XX:XX:XX +MY_BTCTRLR_HCI: hci0 +# LogLevel: DEBUG diff --git a/examples/config.py b/examples/config.py new file mode 100755 index 0000000..20e2764 --- /dev/null +++ b/examples/config.py @@ -0,0 +1,21 @@ +#!/usr/bin/python3 +''' +This is an example script to demonstrate the usage of +B00stLogger and B00stConfig +''' + +from pyb00st import B00stLogger +from pyb00st import B00stConfig + + +def get_config(): + '''This function only exists to avoid 'invalid-name' errors from pylint''' + cfg = B00stConfig() + log = B00stLogger().logger + + log.error("MY_MOVEHUB_ADD: %s", cfg.MY_MOVEHUB_ADD) + log.error("MY_BTCTRLR_HCI: %s", cfg.MY_BTCTRLR_HCI) + + +if __name__ == "__main__": + get_config() diff --git a/examples/demo_button_read.py b/examples/demo_button_read.py index 273825a..896bb83 100755 --- a/examples/demo_button_read.py +++ b/examples/demo_button_read.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 +from pyb00st import B00stConfig from pyb00st.movehub import MoveHub from pyb00st.constants import * from time import sleep -MY_MOVEHUB_ADD = '00:16:53:A4:CD:7E' -MY_BTCTRLR_HCI = 'hci0' +CFG = B00stConfig() -mymovehub = MoveHub(MY_MOVEHUB_ADD, 'BlueZ', MY_BTCTRLR_HCI) +mymovehub = MoveHub(CFG.MY_MOVEHUB_ADD, 'BlueZ', CFG.MY_BTCTRLR_HCI) try: mymovehub.start() @@ -23,5 +23,7 @@ print('RELEASED') else: print('') +except KeyboardInterrupt: + print("\nCTRL-C pressed! Exiting ...") finally: mymovehub.stop() diff --git a/examples/demo_colordist.py b/examples/demo_colordist.py index d17aac5..c565d3a 100755 --- a/examples/demo_colordist.py +++ b/examples/demo_colordist.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 +from pyb00st import B00stConfig from pyb00st.movehub import MoveHub -from pyb00st.constants import * +from pyb00st.constants import PORT_C from time import sleep -MY_MOVEHUB_ADD = '00:16:53:A4:CD:7E' -MY_BTCTRLR_HCI = 'hci0' +CFG = B00stConfig() -mymovehub = MoveHub(MY_MOVEHUB_ADD, 'BlueZ', MY_BTCTRLR_HCI) +mymovehub = MoveHub(CFG.MY_MOVEHUB_ADD, 'BlueZ', CFG.MY_BTCTRLR_HCI) try: mymovehub.start() @@ -19,5 +19,7 @@ sleep(0.2) print('Color: {} Distance: {}'.format(mymovehub.last_color_C, mymovehub.last_distance_C)) +except KeyboardInterrupt: + print("\nCTRL-C pressed! Exiting ...") finally: mymovehub.stop() diff --git a/examples/demo_encoder_and_color.py b/examples/demo_encoder_and_color.py index 77d832c..c587158 100755 --- a/examples/demo_encoder_and_color.py +++ b/examples/demo_encoder_and_color.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 +from pyb00st import B00stConfig from pyb00st.movehub import MoveHub -from pyb00st.constants import * +from pyb00st.constants import PORT_D, PORT_C from time import sleep -MY_MOVEHUB_ADD = '00:16:53:A4:CD:7E' -MY_BTCTRLR_HCI = 'hci0' +CFG = B00stConfig() -mymovehub = MoveHub(MY_MOVEHUB_ADD, 'BlueZ', MY_BTCTRLR_HCI) +mymovehub = MoveHub(CFG.MY_MOVEHUB_ADD, 'BlueZ', CFG.MY_BTCTRLR_HCI) try: mymovehub.start() @@ -19,5 +19,7 @@ while True: sleep(0.2) print('Motor D: {} Color: {}'.format(mymovehub.last_angle_D, mymovehub.last_color_C)) +except KeyboardInterrupt: + print("\nCTRL-C pressed! Exiting ...") finally: mymovehub.stop() diff --git a/examples/demo_encoder_read.py b/examples/demo_encoder_read.py index f3b9db9..e9a5bec 100755 --- a/examples/demo_encoder_read.py +++ b/examples/demo_encoder_read.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 +from pyb00st import B00stConfig from pyb00st.movehub import MoveHub from pyb00st.constants import * from time import sleep -MY_MOVEHUB_ADD = '00:16:53:A4:CD:7E' -MY_BTCTRLR_HCI = 'hci0' +CFG = B00stConfig() -mymovehub = MoveHub(MY_MOVEHUB_ADD, 'BlueZ', MY_BTCTRLR_HCI) +mymovehub = MoveHub(CFG.MY_MOVEHUB_ADD, 'BlueZ', CFG.MY_BTCTRLR_HCI) try: mymovehub.start() @@ -18,5 +18,7 @@ while True: sleep(0.2) print(mymovehub.last_angle_C) +except KeyboardInterrupt: + print("\nCTRL-C pressed! Exiting ...") finally: mymovehub.stop() diff --git a/examples/demo_motor.py b/examples/demo_motor.py index 77f3303..a7218e0 100755 --- a/examples/demo_motor.py +++ b/examples/demo_motor.py @@ -1,54 +1,64 @@ #!/usr/bin/env python3 +'''Example script to show how to control motors''' +from time import sleep + +from pyb00st import B00stConfig from pyb00st.movehub import MoveHub -from pyb00st.constants import * +from pyb00st.constants import MOTOR_A, MOTOR_AB + + +def run(): + ''' + This function is required for pylint, without cfg and mymovehub are + treated as constants + ''' + cfg = B00stConfig() + mymovehub = MoveHub(cfg.MY_MOVEHUB_ADD, 'BlueZ', cfg.MY_BTCTRLR_HCI) + + try: + mymovehub.start() + + # turn motor A ON for 1000 ms at 100% duty cycle in both directions + mymovehub.run_motor_for_time(MOTOR_A, 1000, 100) + sleep(1) + mymovehub.run_motor_for_time(MOTOR_A, 1000, -100) + sleep(1) + + sleep(0.5) + + # rotate motor 90 degrees at 100% duty cycle in both directions + mymovehub.run_motor_for_angle(MOTOR_A, 90, 100) + sleep(0.5) + mymovehub.run_motor_for_angle(MOTOR_A, 90, -100) + + sleep(0.5) + + # turn pair AB ON for 1000 ms at 100% duty cycle in both direction + mymovehub.run_motors_for_time(MOTOR_AB, 1000, 100, 100) + sleep(1) + mymovehub.run_motors_for_time(MOTOR_AB, 1000, 100, -100) + sleep(1) + mymovehub.run_motors_for_time(MOTOR_AB, 1000, -100, -100) + sleep(1) + mymovehub.run_motors_for_time(MOTOR_AB, 1000, -100, 100) + sleep(1) + + sleep(0.5) + + # rotate pair AB 90 degrees at 100% duty cycle in both direction + mymovehub.run_motors_for_angle(MOTOR_AB, 90, 100, 100) + sleep(0.5) + mymovehub.run_motors_for_angle(MOTOR_AB, 90, 100, -100) + sleep(0.5) + mymovehub.run_motors_for_angle(MOTOR_AB, 90, -100, -100) + sleep(0.5) + mymovehub.run_motors_for_angle(MOTOR_AB, 90, -100, 100) + sleep(0.5) + + finally: + mymovehub.stop() -from time import sleep -MY_MOVEHUB_ADD = '00:16:53:A4:CD:7E' -MY_BTCTRLR_HCI = 'hci0' - -mymovehub = MoveHub(MY_MOVEHUB_ADD, 'BlueZ', MY_BTCTRLR_HCI) - -try: - mymovehub.start() - - # turn motor A ON for 1000 ms at 100% duty cycle in both directions - mymovehub.run_motor_for_time(MOTOR_A, 1000, 100) - sleep(1) - mymovehub.run_motor_for_time(MOTOR_A, 1000, -100) - sleep(1) - - sleep(0.5) - - # rotate motor 90 degrees at 100% duty cycle in both directions - mymovehub.run_motor_for_angle(MOTOR_A, 90, 100) - sleep(0.5) - mymovehub.run_motor_for_angle(MOTOR_A, 90, -100) - - sleep(0.5) - - # turn pair AB ON for 1000 ms at 100% duty cycle in both direction - mymovehub.run_motors_for_time(MOTOR_AB, 1000, 100, 100) - sleep(1) - mymovehub.run_motors_for_time(MOTOR_AB, 1000, 100, -100) - sleep(1) - mymovehub.run_motors_for_time(MOTOR_AB, 1000, -100, -100) - sleep(1) - mymovehub.run_motors_for_time(MOTOR_AB, 1000, -100, 100) - sleep(1) - - sleep(0.5) - - # rotate pair AB 90 degrees at 100% duty cycle in both direction - mymovehub.run_motors_for_angle(MOTOR_AB, 90, 100, 100) - sleep(0.5) - mymovehub.run_motors_for_angle(MOTOR_AB, 90, 100, -100) - sleep(0.5) - mymovehub.run_motors_for_angle(MOTOR_AB, 90, -100, -100) - sleep(0.5) - mymovehub.run_motors_for_angle(MOTOR_AB, 90, -100, 100) - sleep(0.5) - -finally: - mymovehub.stop() +if __name__ == '__main__': + run() diff --git a/examples/demo_tilt_read.py b/examples/demo_tilt_read.py index bceb447..c3bb053 100755 --- a/examples/demo_tilt_read.py +++ b/examples/demo_tilt_read.py @@ -1,16 +1,14 @@ #!/usr/bin/env python3 +from pyb00st import B00stConfig from pyb00st.movehub import MoveHub -from pyb00st.constants import * +from pyb00st.constants import MODE_HUBTILT_BASIC, TILT_BASIC_VALUES, TILT_BASIC_TEXT from time import sleep -MY_MOVEHUB_ADD = '00:16:53:A4:CD:7E' -MY_BTCTRLR_HCI = 'hci0' +CFG = B00stConfig() -#mymovehub = MoveHub(MY_MOVEHUB_ADD, 'BlueZ', MY_BTCTRLR_HCI) -#mymovehub = MoveHub(MY_MOVEHUB_ADD, 'BlueGiga', '') -mymovehub = MoveHub(MY_MOVEHUB_ADD, 'Auto', MY_BTCTRLR_HCI) +mymovehub = MoveHub(CFG.MY_MOVEHUB_ADD, 'Auto', CFG.MY_BTCTRLR_HCI) try: mymovehub.start() @@ -22,6 +20,7 @@ sleep(0.2) if mymovehub.last_hubtilt in TILT_BASIC_VALUES: print(TILT_BASIC_TEXT[mymovehub.last_hubtilt]) +except KeyboardInterrupt: + print("\nCTRL-C pressed! Exiting ...") finally: mymovehub.stop() - diff --git a/examples/test_connection.py b/examples/test_connection.py index 417f36e..9ff6aca 100755 --- a/examples/test_connection.py +++ b/examples/test_connection.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 +from pyb00st import B00stConfig from pyb00st.movehub import MoveHub from pyb00st.constants import * from time import sleep -MY_MOVEHUB_ADD = '00:16:53:A4:CD:7E' -MY_BTCTRLR_HCI = 'hci0' +CFG = B00stConfig() -mymovehub = MoveHub(MY_MOVEHUB_ADD, 'BlueZ', MY_BTCTRLR_HCI) +mymovehub = MoveHub(CFG.MY_MOVEHUB_ADD, 'BlueZ', CFG.MY_BTCTRLR_HCI) try: mymovehub.start() diff --git a/pyb00st/__init__.py b/pyb00st/__init__.py index e69de29..f3bcdd2 100644 --- a/pyb00st/__init__.py +++ b/pyb00st/__init__.py @@ -0,0 +1,79 @@ +import sys +import os +import logging +from pathlib import Path +from yaml import load + +try: + from yaml import CLoader as Loader +except ImportError: + from yaml import Loader + + +class ConfigError(Exception): + """Exception raised on errors in configuration""" + pass + +class B00stLogger: + class __B00stLogger: + def __init__(self): + self.logger = logging.getLogger('pyb00st') + self.logger.setLevel(os.getenv('PYB00ST_LOGLEVEL', 'ERROR')) + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + ch = logging.StreamHandler() + ch.setFormatter(formatter) + self.logger.addHandler(ch) + def __str__(self): + return repr(self) + self.val + instance = None + def __init__(self): + if not B00stLogger.instance: + B00stLogger.instance = B00stLogger.__B00stLogger() + + def __getattr__(self, name): + return getattr(self.instance, name) + +class B00stConfig: + class __B00stConfig: + def __init__(self): + logger = B00stLogger().logger + self.MY_MOVEHUB_ADD = '' + self.MY_BTCTRLR_HCI = '' + home = str(Path.home()) + dir_path = os.path.dirname(os.path.realpath(__file__)) + + configs = [ + home + '.config/pyb00st/config.yml', + '/etc/pyb00st/config.yml', + dir_path + '/../etc/config.yml' + ] + config = 0 + for cfg_file in configs: + logger.debug("Checking for file '%s'", cfg_file) + if os.path.isfile(cfg_file): + config = cfg_file + break + + if config: + stream = open(config) + cfg = load(stream, Loader=Loader) + for key in ('MY_MOVEHUB_ADD', 'MY_BTCTRLR_HCI'): + if not cfg[key]: + raise ConfigError("Mandatory parameter '"+key+"'"+ + " not found in configuration") + else: + logger.debug("Setting '%s' to '%s'" % (key, cfg[key])) + setattr(self, key, cfg[key]) + if cfg['LogLevel'] and not os.environ.get('PYB00ST_LOGLEVEL'): + logger.setLevel(cfg['LogLevel']) + else: + raise ConfigError("No configuration file found") + def __str__(self): + return repr(self) + self.val + instance = None + def __init__(self): + if not B00stConfig.instance: + B00stConfig.instance = B00stConfig.__B00stConfig() + + def __getattr__(self, name): + return getattr(self.instance, name) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..470116d --- /dev/null +++ b/setup.py @@ -0,0 +1,16 @@ +'''setup.py - build and install libraries''' + +from distutils.core import setup + +setup( + name='pyb00st', + version='0.1.0', + packages=['pyb00st'], + license='MIT License', + long_description=open('README.md').read(), + url="git@github.com:M0ses/pyb00st", + author='Jorge Pereira ', + author_email='...', + maintainer="Frank Schreiner", + maintainer_email="fschreiner@suse.de" +)