diff --git a/.gitignore b/.gitignore index ca1efaf5c628e939716b11df1b3c11ab37153d45..8a99bb3ba5af893f4561dd0f59db20e3bd1dd19d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,12 @@ -**/.idea/ +.idea **/__pycache__/ **/logs/ **/log/ **/build/ **/*.egg-info/ -**/venv*/ -**/dist +dist +wiki + +*.pyc + +test/server/server_config.toml \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ea6658275fb217633bc3d874e4d07c3316ed7534..38167504d3a4ca99825c25e2c17173494dd2edeb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -18,7 +18,7 @@ Build Wheel: - master - legacy script: - - '& cmd /k "C:\Anaconda3\Scripts\activate.bat E:\environments\$env:UUID & python setup.py sdist bdist_wheel"' + - python setup.py sdist bdist_wheel Copy To Package Registry: image: python:3.9 @@ -32,24 +32,25 @@ Copy To Package Registry: - master - legacy script: - - '& cmd /k "C:\Anaconda3\Scripts\activate.bat E:\environments\$env:UUID & pip install twine & python -m twine upload -u gitlab-ci-token -p $CI_JOB_TOKEN --repository-url https://git-ce.rwth-aachen.de/api/v4/projects/${CI_PROJECT_ID}/packages/pypi dist/*"' - -pages: - image: python:3.9 - stage: deploy - tags: - - frodo - only: - - master - script: - - pip3 install -r requirements.txt - - mkdir public - - cd doc - - make html - - cp -r ./build/html/. ../public - artifacts: - paths: - - public + - pip install twine + - python -m twine upload -u gitlab-ci-token -p $CI_JOB_TOKEN --repository-url https://git-ce.rwth-aachen.de/api/v4/projects/${CI_PROJECT_ID}/packages/pypi dist/* + +#pages: +# image: python:3.9 +# stage: deploy +# tags: +# - frodo +# only: +# - master +# script: +# - pip3 install -r requirements.txt +# - mkdir public +# - cd doc +# - make html +# - cp -r ./build/html/. ../public +# artifacts: +# paths: +# - public diff --git a/README.md b/README.md index 68ec6ba7c93df58457894a39879126545258f268..b2a2e68fbd653356b79c7d75eeb4bad0f1810c55 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [](https://git-ce.rwth-aachen.de/wzl-mq-ms/forschung-lehre/lava/unified-device-interface/python/commits/master) # Python Unified Device Interface -Current stable version: 6.1.1 +Current stable version: 6.1.2 Stable legacy version: 5.2.7 ## Installation @@ -20,46 +20,52 @@ pip install --extra-index-url https://package-read:sTkDQPiyBqkUyYVXZfeK@git-ce.r ## Usage For using the PUDI device interface you need to have a SOIL-Model of your device in JSON-Format. See the Wiki for more information. -You can design the model by hand our use the [SOIL-Editor](https://git-ce.rwth-aachen.de/wzl-mq-ms/forschung-lehre/modelizer), to generate the source code and get a ready-to-run python script for your device server. +You can design the model by hand our use the [SOIL-Web-Editor](https://iot.wzl-mq.rwth-aachen.de/soil/), to generate the source code and get a ready-to-run python script for your device server. Please keep in mind, that creating a service with the SOIL-Editor gives your more features you can use. ### Manual setup If you do not use the SOIL-Editor, create a JSON-Model of your device and a mapping dictionary by hand. - Please refer to the Servetize section of the repository's Wiki. -Setup your server as follows: - -```python - # set up servers - executor = ThreadPoolExecutor(max_workers=100) - loop = asyncio.get_event_loop() - loop.set_default_executor(executor) - - # configure model - macs = ['AC-DE-48-00-00-80', 'AE-DE-48-00-00-80', 'BC-DE-48-00-00-80'] - sensors = [Sensor(macs[i]) for i in range(3)] - implementation = Environment(sensors) - - # configure HTTP server - mapping = src.test.devices.mapping.mapping(implementation) - model = Component.load('./src/test/devices/environmental_sensor/Environment.json', mapping) - http = HTTPServer(loop, HTTP_ADDRESS, HTTP_PORT, model) - - # start servers - try: - loop.run_forever() - except: - pass - finally: - loop.close() -``` + Please refer to the Servetize section of the repository's Wiki. Additionally, you can check the _example_ directory for exemplary implementation(s). + +## Citation & References + +Scientific background and publications related to the _(Python) Unified Device Interface_ are: + +[Bodenbenner, M.](mailto:m.bodenbenner@wzl-mq.rwth-aachen.de); Sanders, M. P.; Montavon, B.; Schmitt, R. H. (2021): +Domain-Specific Language for Sensors in the Internet of Production. +In: Bernd-Arno Behrens, Alexander Brosius, Wolfgang Hintze, Steffen Ihlenfeldt und Jens Peter Wulfsberg (Hg.): +Production at the leading edge of technology. Proceedings of the 10th Congress of the German Academic Association for Production Technology (WGP), Dresden, 23-24 September 2020. Berlin, Heidelberg, 2021. 1st ed. 2021. Berlin, Heidelberg: Springer (Lecture Notes in Production Engineering), S. 448–456, +http://dx.doi.org/10.1007/978-3-662-62138-7_45 + +[Bodenbenner, M.](mailto:m.bodenbenner@wzl-mq.rwth-aachen.de); Montavon, B.; Schmitt, R.H. (2021): +FAIR sensor services - Towards sustainable sensor data management. +In: Measurement: Sensors 18, S. 100206, +https://doi.org/10.1016/j.measen.2021.100206 + +[Montavon, B.](mailto:b.montavon@wzl-mq.rwth-aachen.de)(2021): +Virtual Reference Frame Based on Distributed Large-Scale Metrology Providing Coordinates as a Service. +Aachen: Apprimus Verlag, +https://doi.org/10.18154/RWTH-2021-10238 -1. Do not change the three lines below `# set up servers` and below `# start servers`. -2. Replace the lines below `# configure model` by the initialization of your device. -3. Pass your initialized device to the docstring parser for MQTT and HTTP. -4. If you are not using MQTT or HTTP leave out the respective lines. +[Montavon, B.](mailto:b.montavon@wzl-mq.rwth-aachen.de); Peterek, M.; Schmitt, R. H. (2019): +Model-based interfacing of large-scale metrology instruments. +In: Ettore Stella (Hg.): Multimodal Sensing: Technologies and Applications. 26-27 June 2019, Munich, Germany. Multimodal Sensing and Artificial Intelligence: Technologies and Applications. Munich, Germany, 6/24/2019 - 6/27/2019. Bellingham, Washington: SPIE (Proceedings of SPIE. 5200-, volume 11059), S. 11, +https://doi.org/10.1117/12.2527461 + +## Acknowledgements + +The authors acknowledge funding from the LaVA project (Large Volume Applications, contract 17IND03 of the +European Metrology Programme for Innovation and Research EMPIR). The EMPIR initiative is co-funded by +the European Union’s Horizon 2020 research and innovation programme and the EMPIR Participating States. + +Funded by the Deutsche Forschungsgemeinschaft (DFG, German Research Foundation) under Germany's Excellence Strategy – EXC-2023 Internet of Production – 390621612. ## Recent changes +6.1.2 | 5.2.7 - 2022-04-14 + +- bug fix of loop handling of aiohttp web application + 6.1.1 | 5.2.7 - 2021-05-19 - improved error output for developers diff --git a/build.bat b/build.bat deleted file mode 100644 index 1b357428ac738cb4b5d780811941e972bab9ab03..0000000000000000000000000000000000000000 --- a/build.bat +++ /dev/null @@ -1,7 +0,0 @@ -@echo off -rmdir dist /s /q -rmdir build /s /q -rmdir wzl_udi.egg-info /s /q -python setup.py bdist_wheel -:: TODO complete line below -:: copy "../dist/" "D:\users\bdn\Forschungsprojekte\Virtual Metrology Frame\SourceCode\PyPi server" \ No newline at end of file diff --git a/doc/source/index.rst b/doc/source/index.rst index c151183a8d76f00d688cdaf2423acfedfbd31c8b..39b503789c569e5c48ba528069f8ea134f179f94 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -4,7 +4,7 @@ contain the root `toctree` directive. Welcome to Python Unified Device Interface's documentation! -============================================== +=========================================================== .. toctree:: :maxdepth: 2 diff --git a/doc/source/modules.rst b/doc/source/modules.rst deleted file mode 100644 index e9ff8ac1a89c7bd18e69d633121f5a4022ac6fdf..0000000000000000000000000000000000000000 --- a/doc/source/modules.rst +++ /dev/null @@ -1,7 +0,0 @@ -src -=== - -.. toctree:: - :maxdepth: 4 - - src diff --git a/examples/virtual_lasertracker/device/com_base_top.py b/examples/virtual_lasertracker/device/com_base_top.py index d8c1368c471f4bc48b7913cb228b3c1e7a09c142..8337d5355f132171da07504a5d008f01251d454b 100644 --- a/examples/virtual_lasertracker/device/com_base_top.py +++ b/examples/virtual_lasertracker/device/com_base_top.py @@ -1,5 +1,5 @@ from typing import List -from .enums import StateEnum +from device.enums import StateEnum class COMBaseTOP(object): diff --git a/examples/virtual_lasertracker/device/com_basestations.py b/examples/virtual_lasertracker/device/com_basestations.py index d4022e8a98dc3fd0d10f9c31878da73c4422ae01..be47842d356d3558240b533935878d5e4c794da4 100644 --- a/examples/virtual_lasertracker/device/com_basestations.py +++ b/examples/virtual_lasertracker/device/com_basestations.py @@ -1,4 +1,4 @@ -from ..hwc.com_base import COMBase +from hwc.com_base import COMBase class COMBaseStations(object): diff --git a/examples/virtual_lasertracker/device/com_lasertracker_top.py b/examples/virtual_lasertracker/device/com_lasertracker_top.py index d3fb3d57731b12fb143087f899fd64db7f955ec7..cdba60ed14f8d4adae0557634a50b1f69b8cf0b2 100644 --- a/examples/virtual_lasertracker/device/com_lasertracker_top.py +++ b/examples/virtual_lasertracker/device/com_lasertracker_top.py @@ -1,8 +1,8 @@ import datetime -from .com_basestations import COMBaseStations -from .com_mobileentities import COMMobileEntities -from .enums import StateEnum +from device.com_basestations import COMBaseStations +from device.com_mobileentities import COMMobileEntities +from device.enums import StateEnum class COMLasertrackerTOP(object): diff --git a/examples/virtual_lasertracker/device/com_mobileentities.py b/examples/virtual_lasertracker/device/com_mobileentities.py index 45989fd818d14aced1ee7bc0fee4650fdefb5b6e..dfc1eb16c3a37ca71edcc7f2d69f397d30029d3c 100644 --- a/examples/virtual_lasertracker/device/com_mobileentities.py +++ b/examples/virtual_lasertracker/device/com_mobileentities.py @@ -1,5 +1,5 @@ -from ..hwc.com_target import COMTarget -from .enums import StateEnum, ModeEnum +from hwc.com_target import COMTarget +from device.enums import StateEnum, ModeEnum class COMMobileEntities(object): diff --git a/examples/virtual_lasertracker/device/com_target_top.py b/examples/virtual_lasertracker/device/com_target_top.py index 73a58b5d9d83f9834e7243144c1c65e640f3699a..89cf1aabe2404fcda44145a9955b00039b4978ce 100644 --- a/examples/virtual_lasertracker/device/com_target_top.py +++ b/examples/virtual_lasertracker/device/com_target_top.py @@ -1,7 +1,7 @@ from typing import Callable from typing import List -from .enums import StateEnum, ModeEnum +from device.enums import StateEnum, ModeEnum class COMTargetTOP(object): diff --git a/examples/virtual_lasertracker/device/const.py b/examples/virtual_lasertracker/device/const.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/examples/virtual_lasertracker/device/device.py b/examples/virtual_lasertracker/device/device.py index f47c3bd7052c2f922e6efb565bf012d5cf20d27c..2ecd6e613272abb3e7400a17c58687828f55c972 100644 --- a/examples/virtual_lasertracker/device/device.py +++ b/examples/virtual_lasertracker/device/device.py @@ -1,9 +1,7 @@ class Device(object): def __init__(self): - # TODO implement pass def __del__(self): - # TODO implement pass \ No newline at end of file diff --git a/examples/virtual_lasertracker/device/start.py b/examples/virtual_lasertracker/device/start.py index 2835b9e16fbf868f5ad2842162238b204bee5698..78413a99b044f0d52f06d94d2ecddbc49ca9cfb2 100644 --- a/examples/virtual_lasertracker/device/start.py +++ b/examples/virtual_lasertracker/device/start.py @@ -7,17 +7,17 @@ from typing import Dict from wzl.mqtt import MQTTPublisher from wzl.utilities import root_logger -from src.http.server import HTTPServer -from src.soil.component import Component -from src.soil.event import Event, EventSeverity, EventTrigger -from src.soil.stream import FixedJob, EventJob, UpdateJob, StreamScheduler -from .enums import StateEnum -from ..hwc.com_lasertracker import COMLasertracker +from wzl.http.server import HTTPServer +from wzl.soil.component import Component +from wzl.soil.event import Event, EventSeverity, EventTrigger +from wzl.soil.stream import FixedJob, EventJob, UpdateJob, StreamScheduler +from device.enums import StateEnum +from hwc.com_lasertracker import COMLasertracker sys.setswitchinterval(0.0005) -def start(com_lasertracker: COMLasertracker, config: Dict): +def start(com_lasertracker: COMLasertracker, config: Dict, soil_model_file: str): # server settings address = config['http']['address'] port = config['http']['port'] @@ -119,7 +119,7 @@ def start(com_lasertracker: COMLasertracker, config: Dict): submapping['FUN-Trigger'] = { 'method': com_lasertracker._com_mobileentities._com_target[child0_uuid].fun_trigger, 'signature': {'arguments': {}, 'returns': []}, 'mqtt_callback': mqtt.publish} - model = Component.load('../Lasertracker.json', mapping['COM-Lasertracker']) + model = Component.load(soil_model_file, mapping['COM-Lasertracker']) http = HTTPServer(loop, address, port, model) diff --git a/examples/virtual_lasertracker/hwc/com_base.py b/examples/virtual_lasertracker/hwc/com_base.py index c918499fb20366a3bae620c85333d4dd99212d08..9586d6103886d653a74a7ea7aa80a7451c14be0a 100644 --- a/examples/virtual_lasertracker/hwc/com_base.py +++ b/examples/virtual_lasertracker/hwc/com_base.py @@ -1,7 +1,7 @@ import random from math import pi, cos, sin -from ..device.com_base_top import COMBaseTOP +from device.com_base_top import COMBaseTOP class COMBase(COMBaseTOP): diff --git a/examples/virtual_lasertracker/hwc/com_lasertracker.py b/examples/virtual_lasertracker/hwc/com_lasertracker.py index 1887e52c6491c3761a468a5ab14cca6142e76568..a61386873495ca6e0f6d8ddd80cbe3ea667b2284 100644 --- a/examples/virtual_lasertracker/hwc/com_lasertracker.py +++ b/examples/virtual_lasertracker/hwc/com_lasertracker.py @@ -1,6 +1,6 @@ import datetime -from ..device.com_lasertracker_top import COMLasertrackerTOP +from device.com_lasertracker_top import COMLasertrackerTOP class COMLasertracker(COMLasertrackerTOP): diff --git a/examples/virtual_lasertracker/hwc/com_target.py b/examples/virtual_lasertracker/hwc/com_target.py index f0b0f9c06d4cacae5e0f94b0de4ec59d351c3581..923da545be94a8befc9ce92c4793c1268f7d7f77 100644 --- a/examples/virtual_lasertracker/hwc/com_target.py +++ b/examples/virtual_lasertracker/hwc/com_target.py @@ -1,7 +1,7 @@ import json import time -from ..device.com_target_top import COMTargetTOP +from device.com_target_top import COMTargetTOP class COMTarget(COMTargetTOP): diff --git a/examples/virtual_lasertracker/main.py b/examples/virtual_lasertracker/main.py index e9a04b98d820168811b1dbe1754ce504ce77a70b..27c0eb78a47fafe0a5a6106fc8b9609aa1e26728 100644 --- a/examples/virtual_lasertracker/main.py +++ b/examples/virtual_lasertracker/main.py @@ -12,4 +12,4 @@ if __name__ == "__main__": lasertracker = COMLasertracker(device) device.start() - start(lasertracker, config) + start(lasertracker, config, './Lasertracker.json') diff --git a/install.bat b/install.bat deleted file mode 100644 index 838c3f25f548e4f13dec3c559e0c4d0015deca43..0000000000000000000000000000000000000000 --- a/install.bat +++ /dev/null @@ -1 +0,0 @@ -FOR /F "delims=~" %f in (packages.txt) DO conda install --yes "%f" || pip install "%f" \ No newline at end of file diff --git a/setup.py b/setup.py index a2d7aec5f0cdd11e74e9cb045b50539c9b27e457..4ad8f42541498e69c0b16485f1e48416328a17b2 100644 --- a/setup.py +++ b/setup.py @@ -1,13 +1,20 @@ from setuptools import setup, find_packages setup(name='wzl-udi', - version='6.1.1', - url='', + version='6.1.2', + url='', # TODO add url after moving to public group author='Matthias Bodenbenner', - author_email='m.bodenbenner@wzl.rwth-aachen.de', + author_email='m.bodenbenner@wzl.mq.rwth-aachen.de', description='Provides REST-server, publisher-interface and serializer for the Unified Device Interface in Python.', package_dir={'wzl': 'src'}, packages=['wzl.http', 'wzl.soil', 'wzl.utils'], long_description=open('./README.md').read(), - install_requires=['nest-asyncio', 'strict-rfc3339', 'docstring_parser==0.7.1', 'aiohttp', 'wzl-mqtt', 'wzl-utilities', 'deprecated'], + install_requires=['aiohttp==3.8.1', + 'Deprecated==1.2.13', + 'docstring_parser==0.7.3', + 'nest-asyncio==1.4.3', + 'strict-rfc3339==0.7', + 'wzl-mqtt==2.5.1', + 'wzl-utilities==1.2.2' + ], zip_safe=False) diff --git a/src/http/server.py b/src/http/server.py index 225e6684920d4153d242359910fadff5eeca1537..ee6c219bb745a3f328d8dd46204aa439f23f51ce 100644 --- a/src/http/server.py +++ b/src/http/server.py @@ -66,7 +66,7 @@ class HTTPServer(object): self.app.router.add_put(r'/{uuids:(' + BASE_UUID_PATTERN + r'($|/))*}', self.put) self.app.router.add_patch(r'/objects{uuids:(/' + BASE_UUID_PATTERN + r')*/?}', self.patch) self.app.router.add_patch(r'/{uuids:(' + BASE_UUID_PATTERN + r'($|/))*}', self.patch) - web.run_app(self.app, host=self.host, port=self.port) + web.run_app(self.app, host=self.host, port=self.port, loop=loop) logger.info('HTTP-Server serving on {}:{}'.format(host, port)) @staticmethod diff --git a/src/soil/component.py b/src/soil/component.py index 77cd5c2ff485ad8aadbd2b0e56ab71e2a92e8917..c0bb6eb3ed96de73a7e6bb5427f8926b3d91ce04 100644 --- a/src/soil/component.py +++ b/src/soil/component.py @@ -208,13 +208,6 @@ class Component(Element): if 'children' in keys: everything = self._components + self._measurements + self._parameters + self._functions dictionary['children'] = list(map(lambda x: x.serialize(['name', 'uuid']), everything)) - # if 'update' in keys: - # # TODO implement (update all children) - # pass - # # if self._implementation is not None: - # # dict['update'] = self._implementation() - # else: - # dict['children'] = list(map(lambda x: x.serialize(['value', 'uuid']), self._measurements)) return dictionary @staticmethod diff --git a/src/soil/docstring_parser.py b/src/soil/docstring_parser.py index 39c48aa6a5a13ddc33f0db02adf8b51ccffc613d..e1d75596343fc30c2030064b3f7e766949620e9b 100644 --- a/src/soil/docstring_parser.py +++ b/src/soil/docstring_parser.py @@ -89,7 +89,6 @@ def parse_docstrings_for_mqtt(implementation, parent_url="", *args, **kwargs): @deprecated(version='6.0.0', reason='Building service model from docstrings is too error-prone.') def parse_docstrings_for_soil(implementation: Any, parent_url="", *args, **kwargs): - # TODO replace assertions by reasonable error handling if implementation is None: return None diff --git a/src/soil/measurement.py b/src/soil/measurement.py index c38f79210e81eced6e27f6b63d673011e04af087..875980e230f872e7c2d003c5eb2926cfc4bbf664 100644 --- a/src/soil/measurement.py +++ b/src/soil/measurement.py @@ -16,8 +16,8 @@ class Measurement(Figure): if uuid[:3] != 'MEA': raise Exception('{}: The UUID must start with MEA!'.format(uuid)) self._unit = unit - self._covariance = None # TODO init - self._uncertainty = None # TODO init + self._covariance = None + self._uncertainty = None self._timestamp = None self._nonce = nonce @@ -90,7 +90,6 @@ class Measurement(Figure): if not keys: keys = ['uuid', 'name', 'description', 'datatype', 'value', 'dimension', 'range', 'timestamp', 'nonce', 'covariance', 'uncertainty', 'unit', 'ontology'] - # TODO temporary workaround, forces the server to always return the timestamp if the value is queried if 'value' in keys and 'timestamp' not in keys: keys += ['timestamp'] dictionary = {} diff --git a/src/soil/object.py b/src/soil/object.py index ade708a9e5c64fd67d8370958a75588b486da8f5..270af62637922426b3d9819cc368702b07e17856 100644 --- a/src/soil/object.py +++ b/src/soil/object.py @@ -134,13 +134,6 @@ class Object(Element): if 'children' in keys: everything = self._objects + self._variables + self._parameters + self._functions dictionary['children'] = list(map(lambda x: x.serialize(['name', 'uuid']), everything)) - # if 'update' in keys: - # # TODO implement (update all children) - # pass - # # if self._implementation is not None: - # # dict['update'] = self._implementation() - # else: - # dict['children'] = list(map(lambda x: x.serialize(['value', 'uuid']), self._variables)) return dictionary @staticmethod diff --git a/src/soil/variable.py b/src/soil/variable.py index 836796635bb2a972b953353f8cc17a5404d56eb7..0279eaadd31fc142630281f0056c13bd67cfbde5 100644 --- a/src/soil/variable.py +++ b/src/soil/variable.py @@ -15,8 +15,8 @@ class Variable(Figure): def __init__(self, uuid, name, description, datatype, dimension, range, getter, unit, nonce=None, ontology: str = None): Figure.__init__(self, uuid, name, description, datatype, dimension, range, None, getter, ontology) self._unit = unit - self._covariance = None # TODO init - self._uncertainty = None # TODO init + self._covariance = None + self._uncertainty = None self._timestamp = None self._nonce = nonce @@ -89,7 +89,6 @@ class Variable(Figure): if not keys: keys = ['uuid', 'name', 'description', 'datatype', 'value', 'dimension', 'range', 'timestamp', 'nonce', 'covariance', 'uncertainty', 'unit', 'ontology'] - # TODO temporary workaround, forces the server to always return the timestamp if the value is queried if 'value' in keys and 'timestamp' not in keys: keys += ['timestamp'] dictionary = {} diff --git a/start.bat b/start.bat deleted file mode 100644 index 8e8141d68331bb8aa9839da2b7ca4b2961ae07e3..0000000000000000000000000000000000000000 --- a/start.bat +++ /dev/null @@ -1,7 +0,0 @@ -@echo off -TITLE Distributed Environmental Sensor System (Server) -call conda activate pudi -cls -set PYTHONPATH=%PYTHONPATH%;./src -echo ##### Start server... -python main.py 0.0.0.0 8015 diff --git a/test.bat b/test.bat deleted file mode 100644 index 60949afd4ce8845f1701cef0eed7934d34be75bc..0000000000000000000000000000000000000000 --- a/test.bat +++ /dev/null @@ -1,7 +0,0 @@ -@echo off -start cmd /c "TITLE TestServer& call conda activate udi & python main.py localhost 8015" -cd ./test -call conda activate udi -python test.py -set /p DUMMY=Hit ENTER to close the test... -taskkill /IM cmd.exe /FI "WINDOWTITLE eq TestServer" \ No newline at end of file diff --git a/test/__init__.py b/test/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/examples/virtual_lasertracker/__init__.py b/test/server/__init__.py similarity index 100% rename from examples/virtual_lasertracker/__init__.py rename to test/server/__init__.py diff --git a/test/server/main.py b/test/server/main.py new file mode 100644 index 0000000000000000000000000000000000000000..5162fbbd9d226bf55f21f304327f0d6a23e5db4e --- /dev/null +++ b/test/server/main.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +import os +import sys + +import toml as toml + +sys.path.insert(0, os.path.abspath(os.path.join('..', '..', 'examples', 'virtual_lasertracker'))) +sys.path.insert(0, os.path.abspath(os.path.join('..', '..'))) + +from start import start +from hwc.com_lasertracker import COMLasertracker +from lt_device import LTDevice + +if __name__ == "__main__": + + config = toml.load('server_config.toml') + soil_model_file = os.path.join('../..', 'examples', 'virtual_lasertracker', 'Lasertracker.json') + + device = LTDevice() + lasertracker = COMLasertracker(device) + device.start() + + start(lasertracker, config, soil_model_file) diff --git a/test/server/start.py b/test/server/start.py new file mode 100644 index 0000000000000000000000000000000000000000..b4c01c13a23b05e5ad90362bde4fcd6a24c7eb6e --- /dev/null +++ b/test/server/start.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- +import asyncio +import sys +from concurrent.futures import ThreadPoolExecutor +from typing import Dict + +from wzl.mqtt import MQTTPublisher +from wzl.utilities import root_logger + +from src.http.server import HTTPServer +from src.soil.component import Component +from src.soil.event import Event, EventSeverity, EventTrigger +from src.soil.stream import FixedJob, EventJob, UpdateJob, StreamScheduler +from device.enums import StateEnum +from hwc.com_lasertracker import COMLasertracker + +sys.setswitchinterval(0.0005) + + +def start(com_lasertracker: COMLasertracker, config: Dict, soil_model_file: str): + # server settings + address = config['http']['address'] + port = config['http']['port'] + + # mqtt settings + mqtt_host = config['mqtt']['host'] + mqtt_port = config['mqtt']['port'] + mqtt_vhost = config['mqtt']['vhost'] + mqtt_username = config['mqtt']['username'] + mqtt_password = config['mqtt']['password'] + mqtt_ssl = config['mqtt']['ssl'] + mqtt_websockets = config['mqtt']['websockets'] + + main_logger = root_logger.get(__name__) + + # set up servers + executor = ThreadPoolExecutor(max_workers=100) + loop = asyncio.get_event_loop() + loop.set_default_executor(executor) + + # configure mqtt + mqtt = MQTTPublisher(mqtt_username, prefix=mqtt_username) + mqtt.connect(mqtt_host, mqtt_username, mqtt_password, vhost=mqtt_vhost, port=mqtt_port, ssl=mqtt_ssl, + websocket=mqtt_websockets) + + # configure messages + schedule = [] + schedule += [FixedJob("COM-Lasertracker/COM-BaseStations/COM-Base/MEA-Position", 30, + com_lasertracker._com_basestations._com_base.get_mea_position)] + for child0_uuid in com_lasertracker._com_mobileentities._com_target: + schedule += [UpdateJob("COM-Lasertracker/COM-MobileEntities/{}/MEA-Position".format(child0_uuid), + com_lasertracker._com_mobileentities._com_target[child0_uuid].get_mea_position)] + schedule += [UpdateJob("COM-Lasertracker/COM-MobileEntities/{}/MEA-Quaternion".format(child0_uuid), + com_lasertracker._com_mobileentities._com_target[child0_uuid].get_mea_quaternion)] + + # configure events + schedule += [EventJob("COM-Lasertracker/COM-BaseStations/COM-Base/MEA-Distance", 10, + com_lasertracker._com_basestations._com_base.get_mea_distance, + Event(EventSeverity.WARNING, EventTrigger.LARGER, "double", 50, + "The target is distance is large. Uncertainty might be high."))] + schedule += [EventJob("COM-Lasertracker/COM-BaseStations/COM-Base/PAR-State", 10, + com_lasertracker._com_basestations._com_base.get_par_state, + Event(EventSeverity.ERROR, EventTrigger.EQUALS, "enum", StateEnum.ERROR, + "An error occured!"))] + for child0_uuid in com_lasertracker._com_mobileentities._com_target: + schedule += [EventJob("COM-Lasertracker/COM-MobileEntities/{}/PAR-Locked".format(child0_uuid), 10, + com_lasertracker._com_mobileentities._com_target[child0_uuid].get_par_locked, + Event(EventSeverity.WARNING, EventTrigger.EQUALS, "bool", False, + "Target is not locked!"))] + + scheduler = StreamScheduler(loop, schedule, [mqtt], start_immediately=True) + + # configure model + mapping = {} + mapping['COM-Lasertracker'] = {'add': None, 'remove': None} + submapping = mapping['COM-Lasertracker'] + submapping['PAR-State'] = {'getter': com_lasertracker.get_par_state, 'setter': com_lasertracker.set_par_state} + submapping['PAR-Manufacturer'] = {'getter': com_lasertracker.get_par_manufacturer, 'setter': None} + submapping['PAR-Version'] = {'getter': com_lasertracker.get_par_version, 'setter': None} + submapping['PAR-Time'] = {'getter': com_lasertracker.get_par_time, 'setter': com_lasertracker.set_par_time} + submapping['FUN-Reset'] = {'method': com_lasertracker.fun_reset, 'signature': {'arguments': {}, 'returns': []}} + submapping['FUN-Shutdown'] = {'method': com_lasertracker.fun_shutdown, + 'signature': {'arguments': {}, 'returns': []}} + mapping['COM-Lasertracker']['COM-BaseStations'] = {'add': None, 'remove': None} + submapping = mapping['COM-Lasertracker']['COM-BaseStations'] + mapping['COM-Lasertracker']['COM-BaseStations']['COM-Base'] = {'add': None, 'remove': None} + submapping = mapping['COM-Lasertracker']['COM-BaseStations']['COM-Base'] + submapping['MEA-Position'] = com_lasertracker._com_basestations._com_base.get_mea_position + submapping['MEA-Quaternion'] = com_lasertracker._com_basestations._com_base.get_mea_quaternion + submapping['MEA-Azimuth'] = com_lasertracker._com_basestations._com_base.get_mea_azimuth + submapping['MEA-Elevation'] = com_lasertracker._com_basestations._com_base.get_mea_elevation + submapping['MEA-Distance'] = com_lasertracker._com_basestations._com_base.get_mea_distance + submapping['PAR-State'] = {'getter': com_lasertracker._com_basestations._com_base.get_par_state, 'setter': None} + submapping['PAR-Interval'] = {'getter': com_lasertracker._com_basestations._com_base.get_par_interval, + 'setter': com_lasertracker._com_basestations._com_base.set_par_interval} + submapping['FUN-Jog'] = {'method': com_lasertracker._com_basestations._com_base.fun_jog, 'signature': { + 'arguments': {'ARG-Azimuth': 'arg_azimuth', 'ARG-Elevation': 'arg_elevation'}, 'returns': []}} + submapping['FUN-PointTo'] = {'method': com_lasertracker._com_basestations._com_base.fun_pointto, + 'signature': {'arguments': {'ARG-Position': 'arg_position'}, 'returns': []}} + mapping['COM-Lasertracker']['COM-MobileEntities'] = {'add': com_lasertracker._com_mobileentities.add, + 'remove': com_lasertracker._com_mobileentities.remove} + submapping = mapping['COM-Lasertracker']['COM-MobileEntities'] + for child0_uuid in com_lasertracker._com_mobileentities._com_target: + mapping['COM-Lasertracker']['COM-MobileEntities'][child0_uuid] = {'add': None, 'remove': None} + submapping = mapping['COM-Lasertracker']['COM-MobileEntities'][child0_uuid] + submapping['MEA-Position'] = com_lasertracker._com_mobileentities._com_target[child0_uuid].get_mea_position + submapping['MEA-Quaternion'] = com_lasertracker._com_mobileentities._com_target[child0_uuid].get_mea_quaternion + submapping['PAR-State'] = { + 'getter': com_lasertracker._com_mobileentities._com_target[child0_uuid].get_par_state, 'setter': None} + submapping['PAR-Mode'] = {'getter': com_lasertracker._com_mobileentities._com_target[child0_uuid].get_par_mode, + 'setter': None} + submapping['PAR-Type'] = {'getter': com_lasertracker._com_mobileentities._com_target[child0_uuid].get_par_type, + 'setter': None} + submapping['PAR-Locked'] = { + 'getter': com_lasertracker._com_mobileentities._com_target[child0_uuid].get_par_locked, + 'setter': com_lasertracker._com_mobileentities._com_target[child0_uuid].set_par_locked} + submapping['FUN-Reset'] = {'method': com_lasertracker._com_mobileentities._com_target[child0_uuid].fun_reset, + 'signature': {'arguments': {}, 'returns': []}} + submapping['FUN-Trigger'] = { + 'method': com_lasertracker._com_mobileentities._com_target[child0_uuid].fun_trigger, + 'signature': {'arguments': {}, 'returns': []}, 'mqtt_callback': mqtt.publish} + model = Component.load(soil_model_file, mapping['COM-Lasertracker']) + + http = HTTPServer(loop, address, port, model) + + # start servers + main_logger.info("Starting main asynchronous loop") + try: + loop.run_forever() + except: + pass + finally: + loop.close()